cleared previously tracked gitignored files
@@ -1,129 +0,0 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
@@ -1,14 +0,0 @@
|
||||
Copyright (c) 2018, 2019, Kristjan Komloši, Jakob Kosec, Juš Dolžan,
|
||||
TeraHz development team
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -1,23 +0,0 @@
|
||||
# TeraHz
|
||||
|
||||
[](https://terahz.readthedocs.io/en/latest/?badge=latest)
|
||||
|
||||
TeraHz is a low-cost spectrometer based on a Raspberry Pi 3 or 3 B+ and three sensors:
|
||||
+ [__AS7265x__](https://www.tindie.com/products/onehorse/compact-as7265x-spectrometer/)
|
||||
is a 18 channel spectrometer chipset that provides the device with spectral data
|
||||
+ [__VEML6075__](https://www.sparkfun.com/products/15089) is an
|
||||
UVA/UVB sensor
|
||||
+ [__APDS-9301__](https://www.sparkfun.com/products/14350) is a calibrated illuminance (lux) meter that provides the device with reliable readings
|
||||
|
||||
## Why?
|
||||
Because people and institutions could use an affordable and accurate light-analysing device that is also portable, easy to use and simple to assemble. TeraHz was started as an answer to our high school not being able to afford a commercially available solution. One TeraHz spectrometer costs around 150$ in parts, which makes it a competitive alternative to other solutions on the market today.
|
||||
|
||||
## Development team
|
||||
Copyright 2018, 2019
|
||||
|
||||
- Kristjan "cls-02" Komloši (electronics, sensor drivers, backend)
|
||||
- Jakob "D3m1j4ck" Kosec (frontend)
|
||||
|
||||
|
||||
I would also like to thank Juš "ANormalPerson" Dolžan, who decided to leave the
|
||||
team, but helped me a lot with backend development.
|
||||
@@ -1,17 +0,0 @@
|
||||
# app.py - main backend program
|
||||
'''Main TeraHz backend program'''
|
||||
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
|
||||
from flask import Flask
|
||||
import flask
|
||||
import sensors
|
||||
|
||||
app = Flask(__name__)
|
||||
@app.route('/data')
|
||||
def sendData():
|
||||
'''Responder function for /data route'''
|
||||
s = sensors.Spectrometer(path='/dev/serial0', baudrate=115200, tout=1)
|
||||
u = sensors.UVSensor()
|
||||
l = sensors.LxMeter()
|
||||
response = flask.jsonify([s.getData(), l.getData(), u.getABI()])
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
# run.sh - run the backend server
|
||||
cd `dirname $0`
|
||||
sudo gunicorn app:app -b 0.0.0.0:5000 &
|
||||
@@ -1,257 +0,0 @@
|
||||
# sensors.py - a module for interfacing to the sensors
|
||||
'''Module for interfacing with TeraHz sensors'''
|
||||
# Copyright 2019 Kristjan Komloši
|
||||
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
|
||||
import serial as ser
|
||||
import pandas as pd
|
||||
import smbus2
|
||||
|
||||
class Spectrometer:
|
||||
'''Class representing the AS7265X specrometer'''
|
||||
def initializeSensor(self):
|
||||
'''confirm the sensor is responding and proceed\
|
||||
with spectrometer initialization'''
|
||||
try:
|
||||
rstring = 'undefined' # just need it set to a value
|
||||
self.setParameters({'gain': 0})
|
||||
self.serialObject.write(b'AT\n')
|
||||
rstring = self.serialObject.readline().decode()
|
||||
if rstring == 'undefined':
|
||||
raise Exception # sensor didn't respond
|
||||
if rstring == 'OK':
|
||||
pass # handshake passed
|
||||
if rstring == 'ERROR':
|
||||
raise Exception # sensor is in error state
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception ocurred when performing spectrometer handshake')
|
||||
|
||||
def setParameters(self, parameters):
|
||||
'''applies the parameters like LED light and gain to the spectrometer'''
|
||||
try:
|
||||
if 'it_time' in parameters:
|
||||
it_time = int(parameters['it_time'])
|
||||
if it_time <= 0:
|
||||
it_time = 1
|
||||
self.serialObject.write(
|
||||
'ATINTTIME={}\n'.format(str(it_time)).encode())
|
||||
self.serialObject.readline()
|
||||
|
||||
if 'gain' in parameters:
|
||||
gain = int(parameters['gain'])
|
||||
if gain < 0 or gain > 3:
|
||||
gain = 1
|
||||
self.serialObject.write('ATGAIN={}\n'.format(gain).encode())
|
||||
self.serialObject.readline()
|
||||
|
||||
if 'led' in parameters:
|
||||
led = bool(parameters['led'])
|
||||
if led:
|
||||
led = 1
|
||||
else:
|
||||
led = 0
|
||||
self.serialObject.write('ATLED3={}\n'.format(led).encode())
|
||||
self.serialObject.readline()
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured during spectrometer initialization')
|
||||
|
||||
def getData(self):
|
||||
'''Returns spectral data in a pandas DataFrame.'''
|
||||
try:
|
||||
self.serialObject.write(b'ATCDATA\n')
|
||||
rawresp = self.serialObject.readline().decode()
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occurred when polling for spectrometer data')
|
||||
else:
|
||||
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF']
|
||||
realorder = [i for i in 'ABCDEFGHRISJTUVWKL']
|
||||
response = pd.Series(
|
||||
[float(i) / 35.0 for i in rawresp[:-3].split(',')], index=responseorder)
|
||||
return pd.DataFrame(response, index=realorder, columns=['uW/cm^2']).to_dict()['uW/cm^2']
|
||||
|
||||
def __init__(self, path='/dev/ttyUSB0', baudrate=115200, tout=1):
|
||||
self.path = path
|
||||
self.baudrate = baudrate
|
||||
self.timeout = 1
|
||||
try:
|
||||
self.serialObject = ser.Serial(path, baudrate, timeout=tout)
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured when opening the serial port at {}'.format(path))
|
||||
else:
|
||||
self.initializeSensor()
|
||||
|
||||
|
||||
class LxMeter:
|
||||
'''Class representing the APDS-9301 digital photometer.'''
|
||||
def __init__(self, busNumber=1, addr=0x39):
|
||||
self.addr = addr
|
||||
try:
|
||||
# initialize the SMBus interface
|
||||
self.bus = smbus2.SMBus(busNumber)
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured opening the SMBus {}'.format(self.bus))
|
||||
|
||||
try:
|
||||
self.bus.write_byte_data(
|
||||
self.addr, 0xa0, 0x03) # enable the sensor
|
||||
self.setGain(16)
|
||||
except:
|
||||
raise Exception('An exception occured when enabling lux meter')
|
||||
|
||||
def setGain(self, gain):
|
||||
'''Set the sensor gain. Either 1 or 16.'''
|
||||
if gain == 1:
|
||||
try:
|
||||
temp = self.bus.read_byte_data(self.addr, 0xa1)
|
||||
self.bus.write_byte_data(self.addr, 0xa1, 0xef & temp)
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured when setting lux meter gain')
|
||||
if gain == 16:
|
||||
try:
|
||||
temp = self.bus.read_byte_data(self.addr, 0xa1)
|
||||
self.bus.write_byte_data(self.addr, 0xa1, 0x10 | temp)
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured when setting lux meter gain')
|
||||
else:
|
||||
raise Exception('Invalid gain')
|
||||
|
||||
def getGain(self):
|
||||
'''Get the gain from the sensor.'''
|
||||
try:
|
||||
if self.bus.read_byte_data(self.addr, 0xa1) & 0x10 == 0x10:
|
||||
return 16
|
||||
if self.bus.read_byte_data(self.addr, 0xa1) & 0x10 == 0x00:
|
||||
return 1
|
||||
raise Exception('An error occured when getting lux meter gain')
|
||||
# Under normal conditions, this raise is unreachable.
|
||||
except:
|
||||
raise Exception('An error occured when getting lux meter gain')
|
||||
|
||||
def setIntTime(self, time):
|
||||
'''Set the lux sensor integration time. 0 to including 2'''
|
||||
if time < 0 or time > 2:
|
||||
raise Exception('Invalid integration time')
|
||||
try:
|
||||
temp = self.bus.read_byte_data(self.addr, 0xa1)
|
||||
self.bus.write_byte_data(self.addr, 0xa1, (temp & 0xfc) | time)
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured setting lux integration time')
|
||||
|
||||
def getIntTime(self):
|
||||
'''Get the lux sensor integration time.'''
|
||||
try:
|
||||
return self.bus.read_byte_data(self.addr, 0xa1) & 0x03
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured getting lux integration time')
|
||||
|
||||
def getData(self):
|
||||
'''return the calculated lux value'''
|
||||
try:
|
||||
chA = self.bus.read_word_data(self.addr, 0xac)
|
||||
chB = self.bus.read_word_data(self.addr, 0xae)
|
||||
except:
|
||||
raise Exception('An exception occured fetching lux channels')
|
||||
|
||||
# scary computations ahead! refer to the apds-9301 datasheet!
|
||||
if chB / chA <= 0.5 and chB / chA > 0:
|
||||
lux = 0.0304 * chA - 0.062 * chA * (chB / chA)**1.4
|
||||
elif chB / chA <= 0.61 and chB / chA > 0.5:
|
||||
lux = 0.0224 * chA - 0.031 * chB
|
||||
elif chB / chA <= 0.8 and chB / chA > 0.61:
|
||||
lux = 0.0128 * chA - 0.0153 * chB
|
||||
elif chB / chA <= 1.3 and chB / chA > 0.8:
|
||||
lux = 0.00146 * chA - 0.00112 * chB
|
||||
else:
|
||||
lux = 0
|
||||
return lux
|
||||
|
||||
|
||||
class UVSensor:
|
||||
'''Class representing VEML6075 UVA/B meter'''
|
||||
def __init__(self, bus=1, addr=0x10):
|
||||
self.addr = addr
|
||||
try:
|
||||
self.bus = smbus2.SMBus(bus)
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured opening SMBus {}'.format(bus))
|
||||
|
||||
try:
|
||||
# enable the sensor and set the integration time
|
||||
self.bus.write_byte_data(self.addr, 0x00, 0b00010000)
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured when initalizing the UV Sensor')
|
||||
|
||||
def getABI(self):
|
||||
'''Calculates the UVA and UVB irradiances,
|
||||
along with UV index. Returns [a,b,i]'''
|
||||
|
||||
try:
|
||||
# read the raw UVA, UVB and compensation values from the sensor
|
||||
aRaw = self.bus.read_word_data(self.addr, 0x07)
|
||||
bRaw = self.bus.read_word_data(self.addr, 0x09)
|
||||
c1 = self.bus.read_word_data(self.addr, 0x0a)
|
||||
c2 = self.bus.read_word_data(self.addr, 0x0b)
|
||||
except:
|
||||
raise Exception('An exception occured when fetching raw UV data')
|
||||
# scary computations ahead! refer to Vishay app note 84339 and Sparkfun
|
||||
# VEML6075 documentation.
|
||||
|
||||
# compensate for visible and IR noise
|
||||
aCorr = aRaw - 2.22 * c1 - 1.33 * c2
|
||||
bCorr = bRaw - 2.95 * c1 - 1.74 * c2
|
||||
|
||||
# convert values into irradiances
|
||||
a = aCorr * 1.06
|
||||
b = bCorr * 0.48
|
||||
|
||||
# zero out negative results (readings with no uv)
|
||||
if a < 0:
|
||||
a = 0
|
||||
if b < 0:
|
||||
b = 0
|
||||
# last, calculate the UV index
|
||||
i = (a + b) / 2
|
||||
|
||||
return [a, b, i]
|
||||
|
||||
def getA(self):
|
||||
'''Returns UVA value. A getABI() wrapper.'''
|
||||
return self.getABI()[0]
|
||||
|
||||
def getB(self):
|
||||
'''Returns UVB value. A getABI() wrapper.'''
|
||||
return self.getABI()[1]
|
||||
|
||||
def getI(self):
|
||||
'''Returns UV index. A getABI() wrapper.'''
|
||||
return self.getABI()[2]
|
||||
|
||||
def on(self):
|
||||
'''Turns the UV sensor on after shutdown.'''
|
||||
try:
|
||||
# write the default value for power on
|
||||
# no configurable params = no bitmask
|
||||
self.bus.write_byte_data(self.addr, 0x00, 0x10)
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured when turning the UV sensor on')
|
||||
|
||||
def off(self):
|
||||
'''Shuts the UV sensor down.'''
|
||||
try:
|
||||
# write the default value + the shutdown bit
|
||||
# no configurable params = no bitmask
|
||||
self.bus.write_byte_data(self.addr, 0x00, 0x11)
|
||||
except:
|
||||
raise Exception(
|
||||
'An exception occured when shutting the UV sensor down')
|
||||
@@ -1,37 +0,0 @@
|
||||
# storage.py - storage backend for TeraHz
|
||||
'''TeraHz storage backend'''
|
||||
# Copyright Kristjan Komloši 2019
|
||||
# All code in this file is licensed under the ISC license,
|
||||
# provided in LICENSE.txt
|
||||
|
||||
|
||||
import sqlite3
|
||||
class jsonStorage:
|
||||
'''Class for simple sqlite3 database of JSON entries'''
|
||||
def __init__(self, dbFile):
|
||||
'''Storage object constructor. Argument is filename'''
|
||||
self.db = sqlite3.connect(dbFile)
|
||||
|
||||
def listJSONs(self):
|
||||
'''Returns a list of all existing entries.'''
|
||||
c = self.db.cursor()
|
||||
c.execute('SELECT * FROM storage')
|
||||
result = c.fetchall()
|
||||
c.close()
|
||||
return result
|
||||
|
||||
def storeJSON(self, jsonString, comment):
|
||||
'''Stores a JSON entry along with a timestamp and a comment.'''
|
||||
c = self.db.cursor()
|
||||
c.execute(('INSERT INTO storage VALUES (datetime'
|
||||
'(\'now\', \'localtime\'), ?, ?)'), (comment, jsonString))
|
||||
c.close()
|
||||
self.db.commit()
|
||||
|
||||
def retrieveJSON(self, timestamp):
|
||||
'''Retrieves a JSON entry. Takes a timestamp string'''
|
||||
c = self.db.cursor()
|
||||
c.execute('SELECT * FROM storage WHERE timestamp = ?', (timestamp,))
|
||||
result = c.fetchall()
|
||||
c.close()
|
||||
return result
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
# Minimal flup configuration for Flask
|
||||
from flup.server.fcgi import WSGIServer
|
||||
from app import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
WSGIServer(app, bindAddress='/var/www/api/terahz.sock').run()
|
||||
@@ -1,9 +0,0 @@
|
||||
# TeraHz build guide
|
||||
In its early development phase, TeraHz was hard and time-consuming to compile and install.
|
||||
This is not case now, as the more optimized DietPi Linux distribution allows
|
||||
better performance and simpler configuration than formerly used Raspbian.
|
||||
|
||||
## Downloading the preconfigured image
|
||||
DietPi needs some initial configuration to support TeraHz. To shorten the process,
|
||||
Preconfigured SD card images are available for download under the release tab in
|
||||
the Github repository
|
||||
@@ -1,26 +0,0 @@
|
||||
# Development-stable dependencies
|
||||
The current development version of TeraHz has been verified to work with:
|
||||
|
||||
- Raspbian Stretch (9)
|
||||
- Python 3.6.8 (built from source code and altinstall'd)
|
||||
- Module versions (direct `pip3.6 list` output):
|
||||
|
||||
```
|
||||
Package Version
|
||||
--------------- ---------
|
||||
Click 7.0
|
||||
Flask 1.0.3
|
||||
itsdangerous 1.1.0
|
||||
Jinja2 2.10.1
|
||||
MarkupSafe 1.1.1
|
||||
numpy 1.16.4
|
||||
pandas 0.24.2
|
||||
pip 18.1
|
||||
pyserial 3.4
|
||||
python-dateutil 2.8.0
|
||||
pytz 2019.1
|
||||
setuptools 40.6.2
|
||||
six 1.12.0
|
||||
smbus 1.1.post2
|
||||
Werkzeug 0.15.4
|
||||
```
|
||||
@@ -1,95 +0,0 @@
|
||||
# TeraHz developer's guide
|
||||
This document explains how TeraHz works. It's a good starting point for developers
|
||||
and an interesting read for the curious.
|
||||
|
||||
# Hardware
|
||||
TeraHz was developed on and for the Raspberry Pi 3 Model B+. Compatibility with
|
||||
other Raspberries can probably be achieved by tweaking the device paths in the
|
||||
`app.py` file, but isn't confirmed at this point. Theoretically, 3 Model B and
|
||||
Zero W should work out of the box, but models without Wi-Fi will need an
|
||||
external Wi-Fi adapter if Wi-Fi functionality is desired. The practicality of
|
||||
compiling Python on the first generation of Raspberry Pis is also very
|
||||
questionable.
|
||||
|
||||
Sensors required for operation are:
|
||||
+ AS7265x
|
||||
+ VEML6075
|
||||
+ APDS-9301
|
||||
|
||||
They provide the spectrometry data, UV data and illuminance data, respectively.
|
||||
They all support I2C, AS7265x supports UART in addition.
|
||||
|
||||
The sensors leech power from the GPIO connector, thus eliminating the need for a
|
||||
separate power supply. The necessary power for the whole system is delivered through
|
||||
the Raspberry's USB port. This also allows for considerable versatility, as it
|
||||
enables the resulting device to be either wall-powered or battery-powered.
|
||||
In a portable configuration, I used a one-cell power bank, which allowed for
|
||||
about 45 minutes of continuous operation.
|
||||
|
||||
## AS7265x chipset
|
||||
_[Datasheet][1ds] [Buy breakout board][1]_
|
||||
|
||||
This chipset supports either I2C or UART. Because transferring large amounts of
|
||||
data over I2C is rather cumbersome, TeraHz uses AS7265x in UART mode.
|
||||
|
||||
This chipset consists of three rather small surface-mounted chips and requires
|
||||
an EEPROM. To lower the complexity of assembly for the end-user, I recommend
|
||||
using a breakout board.
|
||||
|
||||
The serial UART connection operates at 115200 baud, which seems to be the
|
||||
standard for most recent embedded peripherals. As with most serial hardware,
|
||||
the TxD and RxD lines must be crossed over when connecting to the processor.
|
||||
|
||||
Communication with the sensor is simple and clear through AT commands. There's
|
||||
a lot of them, all documented inside the datasheet, but the most important one
|
||||
is `ATGETCDATA`, which returns the calibrated spectral data from the sensors.
|
||||
|
||||
The data is returned in the form of a comma-separated list of floating point
|
||||
values, ending with a newline. The order is alphabetical, which is __different
|
||||
from wavelength order__. See the datasheet for more information.
|
||||
|
||||
## VEML6075
|
||||
_[Datasheet][2ds] [Buy breakout board][2]_
|
||||
|
||||
This chip communicates through I2C and provides TeraHz with UVA and UVB
|
||||
irradiance readings. It's not an ideal chip for this task, as it's been marked
|
||||
End-of-Life by Vishay and it'll have to be replaced with a better one in future
|
||||
hardware versions of TeraHz.
|
||||
|
||||
The chip resides at the I2C address `0x10`. There's not a lot of communication
|
||||
required: at initialization, the integration time has to be set and after that,
|
||||
the sensor is ready to go.
|
||||
|
||||
16-bit UV values lie in two two-byte registers, `0x07` for UVA and `0x09` for
|
||||
UVB. For correct result conversion, there are also two correction registers,
|
||||
UVCOMP_1 and 2, located at `0x0A` and `0x0B`, respectively.
|
||||
|
||||
To convert these four values into irradiances, they must be multiplied by
|
||||
certain constants, somewhat loosely defined in the sensor datasheet. Keep in
|
||||
mind that the way of computing the "irradiance" is very much experimentally
|
||||
derived, and even Vishay's tech support doesn't know how exactly to calculate
|
||||
the irradiance.
|
||||
|
||||
## APDS-9301
|
||||
_[Datasheet][3ds] [Buy breakout board][3]_
|
||||
This chip measures illuminance in luxes and like the VEML6075, connects through
|
||||
I2C. Unlike the VEML6075, this chip is very good at its job, providing accurate
|
||||
and fast results without undefined mathematics or required calibration.
|
||||
|
||||
At power-on, it needs to be enabled and the sensor gain set to the high setting,
|
||||
as the formula for Lux calculation is only defined for that setting. This
|
||||
initialization is handled by the sensors module.
|
||||
|
||||
The lux reading is derived from two channels, descriptively called CH0 and CH1,
|
||||
residing in respective 16-bit registers at addresses `0xAC` and `0xAE`. After a
|
||||
successful read of both data registers, the lux value can be derived using the
|
||||
formula in the sensor's datasheet.
|
||||
|
||||
|
||||
|
||||
[1]: https://www.tindie.com/products/onehorse/compact-as7265x-spectrometer/
|
||||
[2]: https://www.sparkfun.com/products/15089
|
||||
[3]: https://www.sparkfun.com/products/14350
|
||||
[1ds]: sensor-docs/AS7265x.pdf
|
||||
[2ds]: sensor-docs/veml6075.pdf
|
||||
[3ds]: sensor-docs/APDS-9301.pdf
|
||||
@@ -1,55 +0,0 @@
|
||||
# TeraHz Electrical Guide
|
||||
This section briefly explains the neccessary electrical connections between the
|
||||
Raspberry Pi and the sensors you'll need to make to ensure correct and safe
|
||||
operation.
|
||||
|
||||
As mentioned before, TeraHz requires 3 sensors to operate. The simpler UVA/UVB
|
||||
sensor and the ambient light analyzer connect to the Raspberry's SMBus (I2C)
|
||||
bus, while the spectrometer connects via high-speed UART.
|
||||
|
||||

|
||||
|
||||
## PCBs vs breakout boards & jumpers
|
||||
The Raspberry Pi GPIO port includes enough power pins to require only jumper
|
||||
cables to connect the sensors to the Raspberry Pi. However, this is not a great
|
||||
idea. During development, jumper cables have repeatedly been proven to be an
|
||||
unreliable nuisance, and their absolute lack of rigidity helped me fry one of my
|
||||
development Raspberry Pis. For this reason, I wholeheartedly recommend using a
|
||||
simple PCB to route the connections from the Pi to the sensors. At this time,
|
||||
there is no official TeraHz PCB, but it shall be announced and included in the
|
||||
project when basic testing will be done.
|
||||
|
||||
GPIO can be routed to the PCB with a standard old IDE disk cable, and terminated
|
||||
with another 40-pin connector at the PCB. Sensor breakouts should be mounted
|
||||
<<<<<<< HEAD
|
||||
through standard 0.1" connectors, male on the sensor breakout and female on the
|
||||
PCB. A shitty add-on header and a shitty add-on header v1.69bis can't hurt, either.
|
||||
=======
|
||||
through standard 0.1" connectors, male on the sensor brakout and female on the
|
||||
PCB. A shitty addon header and a shitty addon header v1.69bis can't hurt, either.
|
||||
>>>>>>> fd1f07d40dace3e003e49377d4771de53f8bdeb8
|
||||
|
||||
## SMBus sensors
|
||||
SMBus is a well-defined version of the well-known I2C bus, widely used
|
||||
in computer motherboards for low-band bandwidth communication with various ICs,
|
||||
especially sensors and power-supply related devices. This bus is broken out on
|
||||
the Raspberry Pi GPIO port as the "I2C1" bus (see picture).
|
||||
|
||||
Pins are familiarly marked as SDA and SCL, the same as with classic I2C. They
|
||||
connect to the SDA and SCL pins on the VEML6075 and APDS-9301 sensor.
|
||||
|
||||
## UART sensor
|
||||
<<<<<<< HEAD
|
||||
Spectral sensor attaches through the UART port on the Raspberry pi (see picture).
|
||||
=======
|
||||
Spectrometry sensor attaches through the UART port on the Raspberry pi (see picture).
|
||||
>>>>>>> fd1f07d40dace3e003e49377d4771de53f8bdeb8
|
||||
|
||||
The Tx and Rx lines must cross over, connecting the sensor's Tx line to the
|
||||
computer's Rx line and vice versa.
|
||||
|
||||
## Power supply
|
||||
As the sensors require only a small amount of power, they can be powered directly from the Raspberry Pi itself, leeching power from the 3.3V lines.
|
||||
|
||||
## Ground
|
||||
There's not a lot to say here, connect sensor GND to Pi's GND.
|
||||
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 63 KiB |
@@ -1,3 +0,0 @@
|
||||
<img alt="TeraHz logo" src="imgs/logo-sq.png" width="200px">
|
||||
# TeraHz documentation - index
|
||||
This is the starting point of TeraHz documentation.
|
||||
@@ -1,3 +0,0 @@
|
||||
interface=wlan0
|
||||
dhcp-range=192.168.1.10,192.168.1.100,255.255.255.0,24h
|
||||
address=/terahz.site/192.168.1.1
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
# edit_ssid.sh - edits hostapd.conf and sets a MAC address-based SSID
|
||||
cd `dirname $0`
|
||||
ssid=`ip link | awk '/wlan0/ {getline; print $2}' | awk -v FS=':' '{printf("TeraHz_%s%s%s\n", $4, $5, $6)}'`
|
||||
sed "/ssid=.*/s/ssid=.*/ssid=$ssid/" hostapd.conf > newconf
|
||||
mv newconf hostapd.conf
|
||||
@@ -1,9 +0,0 @@
|
||||
interface=wlan0
|
||||
hw_mode=g
|
||||
channel=8
|
||||
wpa=2
|
||||
wpa_key_mgmt=WPA-PSK
|
||||
wpa_pairwise=TKIP
|
||||
rsn_pairwise=CCMP
|
||||
ssid=TeraHz
|
||||
wpa_passphrase=terahertz
|
||||
@@ -1,18 +0,0 @@
|
||||
# install.sh - install TeraHz onto a Raspbian or DietPi installation
|
||||
apt -y update
|
||||
apt -y full-upgrade
|
||||
apt install -y python3 python3-pip lighttpd dnsmasq hostapd libatlas-base-dev
|
||||
pip3 install numpy pandas flask smbus2 pyserial gunicorn
|
||||
|
||||
cp -R hostapd/ /etc
|
||||
chmod +rx /etc/hostapd/edit_ssid.sh
|
||||
cp dnsmasq.conf /etc
|
||||
cp rc.local /etc
|
||||
cp interfaces-terahz /etc/network/interfaces.d/
|
||||
|
||||
cp -R ../frontend/* /var/www/html
|
||||
mkdir -p /usr/local/lib/terahz
|
||||
cp -R ../backend/* /usr/local/lib/terahz
|
||||
|
||||
systemctl unmask dnsmasq hostapd lighttpd
|
||||
systemctl enable dnsmasq hostapd lighttpd
|
||||
@@ -1,3 +0,0 @@
|
||||
iface wlan0 inet static
|
||||
address 192.168.1.1
|
||||
netmask 255.255.255.0
|
||||
@@ -1,27 +0,0 @@
|
||||
server.modules = (
|
||||
"mod_access",
|
||||
"mod_alias",
|
||||
"mod_compress",
|
||||
"mod_redirect"
|
||||
)
|
||||
|
||||
server.document-root = "/var/www/html"
|
||||
server.upload-dirs = ( "/var/cache/lighttpd/uploads" )
|
||||
server.errorlog = "/var/log/lighttpd/error.log"
|
||||
server.pid-file = "/var/run/lighttpd.pid"
|
||||
server.username = "www-data"
|
||||
server.groupname = "www-data"
|
||||
server.port = 80
|
||||
|
||||
|
||||
index-file.names = ( "index.php", "index.html", "index.lighttpd.html" )
|
||||
url.access-deny = ( "~", ".inc" )
|
||||
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
|
||||
|
||||
compress.cache-dir = "/var/cache/lighttpd/compress/"
|
||||
compress.filetype = ( "application/javascript", "text/css", "text/html", "text/plain" )
|
||||
|
||||
# default listening port for IPv6 falls back to the IPv4 port
|
||||
include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port
|
||||
include_shell "/usr/share/lighttpd/create-mime.assign.pl"
|
||||
include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
#
|
||||
# rc.local
|
||||
#
|
||||
# This script is executed at the end of each multiuser runlevel.
|
||||
# Make sure that the script will "exit 0" on success or any other
|
||||
# value on error.
|
||||
#
|
||||
# In order to enable or disable this script just change the execution
|
||||
# bits.
|
||||
#
|
||||
# By default this script does nothing.
|
||||
/etc/hostapd/edit_ssid.sh &
|
||||
/usr/local/lib/terahz/run.sh &
|
||||
sleep 3
|
||||
service hostapd restart # restart hostapd to prevent a weird startup race condition
|
||||
@@ -1,46 +0,0 @@
|
||||
// All code in this file is licensed under the ISC license, provided in LICENSE.txt
|
||||
$('#update').click(function () {
|
||||
updateData();
|
||||
});
|
||||
// jQuery event binder
|
||||
|
||||
function updateData () {
|
||||
const url = 'http://' + window.location.hostname + ':5000/data';
|
||||
$.ajax({ // spawn an AJAX request
|
||||
url: url,
|
||||
success: function (data, status) {
|
||||
console.log(data);
|
||||
graphSpectralData(data[0], 0);
|
||||
fillTableData(data);
|
||||
},
|
||||
timeout: 2500 // this should be a pretty sane timeout
|
||||
});
|
||||
}
|
||||
|
||||
function graphSpectralData (obj, dom) {
|
||||
// graph spectral data in obj into dom
|
||||
var graphPoints = [];
|
||||
var graphXTicks = [];
|
||||
|
||||
Object.keys(obj).forEach((element, index) => {
|
||||
graphPoints.push([index, obj[element]]); // build array of points
|
||||
graphXTicks.push([index, element]); // build array of axis labels
|
||||
});
|
||||
// console.log(graphPoints);
|
||||
const options = {
|
||||
grid: {color: 'white'},
|
||||
xaxis: {ticks: graphXTicks}
|
||||
};
|
||||
$.plot('#graph', [graphPoints], options);
|
||||
// flot expects an array of arrays (lines) of 2-element arrays (points)
|
||||
}
|
||||
|
||||
function fillTableData (obj) {
|
||||
// fill the obj data into HTML tables
|
||||
Object.keys(obj[0])
|
||||
.forEach((element) => { $('#' + element).text(obj[0][element]); });
|
||||
$('#lx').text(obj[1]);
|
||||
$('#uva').text(obj[2][0]);
|
||||
$('#uvb').text(obj[2][1]);
|
||||
$('#uvi').text(obj[2][2]);
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<link rel="stylesheet" href="lib/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="stylesheet.css">
|
||||
<title>TeraHz</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container text-center">
|
||||
<h1><img src="lib/logo-sq.png" height="64px">TeraHz</h1>
|
||||
|
||||
</div>
|
||||
<div class="container">
|
||||
<button id="update" class="btn btn-primary m-1 float-right">Get data</button>
|
||||
<p id="debug">
|
||||
</p>
|
||||
<h3>Spectrogram</h3>
|
||||
<div id="graph" style="height:480px;width:720px"></div>
|
||||
<h3>Spectral readings</h3>
|
||||
<table class="table table-dark table-sm" id="specter">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Band</th>
|
||||
<th>Wavelength [nm]</th>
|
||||
<th>Irradiance [μW/cm²]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>A</td>
|
||||
<td>410 nm</td>
|
||||
<td id="A">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>B</td>
|
||||
<td>435 nm</td>
|
||||
<td id="B">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C</td>
|
||||
<td>460 nm</td>
|
||||
<td id="C">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>D</td>
|
||||
<td>485 nm</td>
|
||||
<td id="D">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>E</td>
|
||||
<td>510 nm</td>
|
||||
<td id="E">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>F</td>
|
||||
<td>535 nm</td>
|
||||
<td id="F">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>G</td>
|
||||
<td>560 nm</td>
|
||||
<td id="G">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H</td>
|
||||
<td>585 nm</td>
|
||||
<td id="H">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>R</td>
|
||||
<td>610 nm</td>
|
||||
<td id="R">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>I</td>
|
||||
<td>645 nm</td>
|
||||
<td id="I">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>S</td>
|
||||
<td>680 nm</td>
|
||||
<td id="S">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>J</td>
|
||||
<td>705 nm</td>
|
||||
<td id="J">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>T</td>
|
||||
<td>730 nm</td>
|
||||
<td id="T">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>U</td>
|
||||
<td>760 nm</td>
|
||||
<td id="U">---</td>
|
||||
</tr>
|
||||
<tr class="table-secondary">
|
||||
<td>V</td>
|
||||
<td>810 nm</td>
|
||||
<td id="V">---</td>
|
||||
</tr>
|
||||
<tr class="table-secondary">
|
||||
<td>W</td>
|
||||
<td>860 nm</td>
|
||||
<td id="W">---</td>
|
||||
</tr>
|
||||
<tr class="table-secondary">
|
||||
<td>K</td>
|
||||
<td>900 nm</td>
|
||||
<td id="K">---</td>
|
||||
</tr>
|
||||
<tr class="table-secondary">
|
||||
<td>L</td>
|
||||
<td>940 nm</td>
|
||||
<td id="L">---</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
<h3>Lux and UV readings</h3>
|
||||
<table class="table-dark table" id="luxuv">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>Illuminance [lx]</td>
|
||||
<td id="lx">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>UVA irradiance [μW/cm²]</td>
|
||||
<td id="uva">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>UVB irradiance [μW/cm²]</td>
|
||||
<td id="uvb">---</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>UVA/UVB average [μW/cm²]</td>
|
||||
<td id="uvi">---</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<script src="lib/bootstrap.bundle.min.js"></script>
|
||||
<script src="lib/jquery-3.4.1.min.js"></script>
|
||||
<script src="lib/flot/jquery.flot.js"></script>
|
||||
<script src="frontend.js"></script>
|
||||
<script src="lib/flot/jquery.canvaswrapper.js"></script>
|
||||
<script src="lib/flot/jquery.colorhelpers.js"></script>
|
||||
<script src="lib/flot/jquery.flot.js"></script>
|
||||
<script src="lib/flot/jquery.flot.saturated.js"></script>
|
||||
<script src="lib/flot/jquery.flot.browser.js"></script>
|
||||
<script src="lib/flot/jquery.flot.drawSeries.js"></script>
|
||||
<script src="lib/flot/jquery.flot.uiConstants.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,550 +0,0 @@
|
||||
/** ## jquery.flot.canvaswrapper
|
||||
|
||||
This plugin contains the function for creating and manipulating both the canvas
|
||||
layers and svg layers.
|
||||
|
||||
The Canvas object is a wrapper around an HTML5 canvas tag.
|
||||
The constructor Canvas(cls, container) takes as parameters cls,
|
||||
the list of classes to apply to the canvas adnd the containter,
|
||||
element onto which to append the canvas. The canvas operations
|
||||
don't work unless the canvas is attached to the DOM.
|
||||
|
||||
### jquery.canvaswrapper.js API functions
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
var Canvas = function(cls, container) {
|
||||
var element = container.getElementsByClassName(cls)[0];
|
||||
|
||||
if (!element) {
|
||||
element = document.createElement('canvas');
|
||||
element.className = cls;
|
||||
element.style.direction = 'ltr';
|
||||
element.style.position = 'absolute';
|
||||
element.style.left = '0px';
|
||||
element.style.top = '0px';
|
||||
|
||||
container.appendChild(element);
|
||||
|
||||
// If HTML5 Canvas isn't available, throw
|
||||
|
||||
if (!element.getContext) {
|
||||
throw new Error('Canvas is not available.');
|
||||
}
|
||||
}
|
||||
|
||||
this.element = element;
|
||||
|
||||
var context = this.context = element.getContext('2d');
|
||||
this.pixelRatio = $.plot.browser.getPixelRatio(context);
|
||||
|
||||
// Size the canvas to match the internal dimensions of its container
|
||||
var width = $(container).width();
|
||||
var height = $(container).height();
|
||||
this.resize(width, height);
|
||||
|
||||
// Collection of HTML div layers for text overlaid onto the canvas
|
||||
|
||||
this.SVGContainer = null;
|
||||
this.SVG = {};
|
||||
|
||||
// Cache of text fragments and metrics, so we can avoid expensively
|
||||
// re-calculating them when the plot is re-rendered in a loop.
|
||||
|
||||
this._textCache = {};
|
||||
}
|
||||
|
||||
/**
|
||||
- resize(width, height)
|
||||
|
||||
Resizes the canvas to the given dimensions.
|
||||
The width represents the new width of the canvas, meanwhile the height
|
||||
is the new height of the canvas, both of them in pixels.
|
||||
*/
|
||||
|
||||
Canvas.prototype.resize = function(width, height) {
|
||||
var minSize = 10;
|
||||
width = width < minSize ? minSize : width;
|
||||
height = height < minSize ? minSize : height;
|
||||
|
||||
var element = this.element,
|
||||
context = this.context,
|
||||
pixelRatio = this.pixelRatio;
|
||||
|
||||
// Resize the canvas, increasing its density based on the display's
|
||||
// pixel ratio; basically giving it more pixels without increasing the
|
||||
// size of its element, to take advantage of the fact that retina
|
||||
// displays have that many more pixels in the same advertised space.
|
||||
|
||||
// Resizing should reset the state (excanvas seems to be buggy though)
|
||||
|
||||
if (this.width !== width) {
|
||||
element.width = width * pixelRatio;
|
||||
element.style.width = width + 'px';
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
if (this.height !== height) {
|
||||
element.height = height * pixelRatio;
|
||||
element.style.height = height + 'px';
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
// Save the context, so we can reset in case we get replotted. The
|
||||
// restore ensure that we're really back at the initial state, and
|
||||
// should be safe even if we haven't saved the initial state yet.
|
||||
|
||||
context.restore();
|
||||
context.save();
|
||||
|
||||
// Scale the coordinate space to match the display density; so even though we
|
||||
// may have twice as many pixels, we still want lines and other drawing to
|
||||
// appear at the same size; the extra pixels will just make them crisper.
|
||||
|
||||
context.scale(pixelRatio, pixelRatio);
|
||||
};
|
||||
|
||||
/**
|
||||
- clear()
|
||||
|
||||
Clears the entire canvas area, not including any overlaid HTML text
|
||||
*/
|
||||
Canvas.prototype.clear = function() {
|
||||
this.context.clearRect(0, 0, this.width, this.height);
|
||||
};
|
||||
|
||||
/**
|
||||
- render()
|
||||
|
||||
Finishes rendering the canvas, including managing the text overlay.
|
||||
*/
|
||||
Canvas.prototype.render = function() {
|
||||
var cache = this._textCache;
|
||||
|
||||
// For each text layer, add elements marked as active that haven't
|
||||
// already been rendered, and remove those that are no longer active.
|
||||
|
||||
for (var layerKey in cache) {
|
||||
if (hasOwnProperty.call(cache, layerKey)) {
|
||||
var layer = this.getSVGLayer(layerKey),
|
||||
layerCache = cache[layerKey];
|
||||
|
||||
var display = layer.style.display;
|
||||
layer.style.display = 'none';
|
||||
|
||||
for (var styleKey in layerCache) {
|
||||
if (hasOwnProperty.call(layerCache, styleKey)) {
|
||||
var styleCache = layerCache[styleKey];
|
||||
for (var key in styleCache) {
|
||||
if (hasOwnProperty.call(styleCache, key)) {
|
||||
var val = styleCache[key],
|
||||
positions = val.positions;
|
||||
|
||||
for (var i = 0, position; positions[i]; i++) {
|
||||
position = positions[i];
|
||||
if (position.active) {
|
||||
if (!position.rendered) {
|
||||
layer.appendChild(position.element);
|
||||
position.rendered = true;
|
||||
}
|
||||
} else {
|
||||
positions.splice(i--, 1);
|
||||
if (position.rendered) {
|
||||
while (position.element.firstChild) {
|
||||
position.element.removeChild(position.element.firstChild);
|
||||
}
|
||||
position.element.parentNode.removeChild(position.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (positions.length === 0) {
|
||||
if (val.measured) {
|
||||
val.measured = false;
|
||||
} else {
|
||||
delete styleCache[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer.style.display = display;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
- getSVGLayer(classes)
|
||||
|
||||
Creates (if necessary) and returns the SVG overlay container.
|
||||
The classes string represents the string of space-separated CSS classes
|
||||
used to uniquely identify the text layer. It return the svg-layer div.
|
||||
*/
|
||||
Canvas.prototype.getSVGLayer = function(classes) {
|
||||
var layer = this.SVG[classes];
|
||||
|
||||
// Create the SVG layer if it doesn't exist
|
||||
|
||||
if (!layer) {
|
||||
// Create the svg layer container, if it doesn't exist
|
||||
|
||||
var svgElement;
|
||||
|
||||
if (!this.SVGContainer) {
|
||||
this.SVGContainer = document.createElement('div');
|
||||
this.SVGContainer.className = 'flot-svg';
|
||||
this.SVGContainer.style.position = 'absolute';
|
||||
this.SVGContainer.style.top = '0px';
|
||||
this.SVGContainer.style.left = '0px';
|
||||
this.SVGContainer.style.height = '100%';
|
||||
this.SVGContainer.style.width = '100%';
|
||||
this.SVGContainer.style.pointerEvents = 'none';
|
||||
this.element.parentNode.appendChild(this.SVGContainer);
|
||||
|
||||
svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svgElement.style.width = '100%';
|
||||
svgElement.style.height = '100%';
|
||||
|
||||
this.SVGContainer.appendChild(svgElement);
|
||||
} else {
|
||||
svgElement = this.SVGContainer.firstChild;
|
||||
}
|
||||
|
||||
layer = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
||||
layer.setAttribute('class', classes);
|
||||
layer.style.position = 'absolute';
|
||||
layer.style.top = '0px';
|
||||
layer.style.left = '0px';
|
||||
layer.style.bottom = '0px';
|
||||
layer.style.right = '0px';
|
||||
svgElement.appendChild(layer);
|
||||
this.SVG[classes] = layer;
|
||||
}
|
||||
|
||||
return layer;
|
||||
};
|
||||
|
||||
/**
|
||||
- getTextInfo(layer, text, font, angle, width)
|
||||
|
||||
Creates (if necessary) and returns a text info object.
|
||||
The object looks like this:
|
||||
```js
|
||||
{
|
||||
width //Width of the text's wrapper div.
|
||||
height //Height of the text's wrapper div.
|
||||
element //The HTML div containing the text.
|
||||
positions //Array of positions at which this text is drawn.
|
||||
}
|
||||
```
|
||||
The positions array contains objects that look like this:
|
||||
```js
|
||||
{
|
||||
active //Flag indicating whether the text should be visible.
|
||||
rendered //Flag indicating whether the text is currently visible.
|
||||
element //The HTML div containing the text.
|
||||
text //The actual text and is identical with element[0].textContent.
|
||||
x //X coordinate at which to draw the text.
|
||||
y //Y coordinate at which to draw the text.
|
||||
}
|
||||
```
|
||||
Each position after the first receives a clone of the original element.
|
||||
The idea is that that the width, height, and general 'identity' of the
|
||||
text is constant no matter where it is placed; the placements are a
|
||||
secondary property.
|
||||
|
||||
Canvas maintains a cache of recently-used text info objects; getTextInfo
|
||||
either returns the cached element or creates a new entry.
|
||||
|
||||
The layer parameter is string of space-separated CSS classes uniquely
|
||||
identifying the layer containing this text.
|
||||
Text is the text string to retrieve info for.
|
||||
Font is either a string of space-separated CSS classes or a font-spec object,
|
||||
defining the text's font and style.
|
||||
Angle is the angle at which to rotate the text, in degrees. Angle is currently unused,
|
||||
it will be implemented in the future.
|
||||
The last parameter is the Maximum width of the text before it wraps.
|
||||
The method returns a text info object.
|
||||
*/
|
||||
Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
|
||||
var textStyle, layerCache, styleCache, info;
|
||||
|
||||
// Cast the value to a string, in case we were given a number or such
|
||||
|
||||
text = '' + text;
|
||||
|
||||
// If the font is a font-spec object, generate a CSS font definition
|
||||
|
||||
if (typeof font === 'object') {
|
||||
textStyle = font.style + ' ' + font.variant + ' ' + font.weight + ' ' + font.size + 'px/' + font.lineHeight + 'px ' + font.family;
|
||||
} else {
|
||||
textStyle = font;
|
||||
}
|
||||
|
||||
// Retrieve (or create) the cache for the text's layer and styles
|
||||
|
||||
layerCache = this._textCache[layer];
|
||||
|
||||
if (layerCache == null) {
|
||||
layerCache = this._textCache[layer] = {};
|
||||
}
|
||||
|
||||
styleCache = layerCache[textStyle];
|
||||
|
||||
if (styleCache == null) {
|
||||
styleCache = layerCache[textStyle] = {};
|
||||
}
|
||||
|
||||
var key = generateKey(text);
|
||||
info = styleCache[key];
|
||||
|
||||
// If we can't find a matching element in our cache, create a new one
|
||||
|
||||
if (!info) {
|
||||
var element = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
if (text.indexOf('<br>') !== -1) {
|
||||
addTspanElements(text, element, -9999);
|
||||
} else {
|
||||
var textNode = document.createTextNode(text);
|
||||
element.appendChild(textNode);
|
||||
}
|
||||
|
||||
element.style.position = 'absolute';
|
||||
element.style.maxWidth = width;
|
||||
element.setAttributeNS(null, 'x', -9999);
|
||||
element.setAttributeNS(null, 'y', -9999);
|
||||
|
||||
if (typeof font === 'object') {
|
||||
element.style.font = textStyle;
|
||||
element.style.fill = font.fill;
|
||||
} else if (typeof font === 'string') {
|
||||
element.setAttribute('class', font);
|
||||
}
|
||||
|
||||
this.getSVGLayer(layer).appendChild(element);
|
||||
var elementRect = element.getBBox();
|
||||
|
||||
info = styleCache[key] = {
|
||||
width: elementRect.width,
|
||||
height: elementRect.height,
|
||||
measured: true,
|
||||
element: element,
|
||||
positions: []
|
||||
};
|
||||
|
||||
//remove elements from dom
|
||||
while (element.firstChild) {
|
||||
element.removeChild(element.firstChild);
|
||||
}
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
|
||||
info.measured = true;
|
||||
return info;
|
||||
};
|
||||
|
||||
function updateTransforms (element, transforms) {
|
||||
element.transform.baseVal.clear();
|
||||
if (transforms) {
|
||||
transforms.forEach(function(t) {
|
||||
element.transform.baseVal.appendItem(t);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- addText (layer, x, y, text, font, angle, width, halign, valign, transforms)
|
||||
|
||||
Adds a text string to the canvas text overlay.
|
||||
The text isn't drawn immediately; it is marked as rendering, which will
|
||||
result in its addition to the canvas on the next render pass.
|
||||
|
||||
The layer is string of space-separated CSS classes uniquely
|
||||
identifying the layer containing this text.
|
||||
X and Y represents the X and Y coordinate at which to draw the text.
|
||||
and text is the string to draw
|
||||
*/
|
||||
Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign, transforms) {
|
||||
var info = this.getTextInfo(layer, text, font, angle, width),
|
||||
positions = info.positions;
|
||||
|
||||
// Tweak the div's position to match the text's alignment
|
||||
|
||||
if (halign === 'center') {
|
||||
x -= info.width / 2;
|
||||
} else if (halign === 'right') {
|
||||
x -= info.width;
|
||||
}
|
||||
|
||||
if (valign === 'middle') {
|
||||
y -= info.height / 2;
|
||||
} else if (valign === 'bottom') {
|
||||
y -= info.height;
|
||||
}
|
||||
|
||||
y += 0.75 * info.height;
|
||||
|
||||
|
||||
// Determine whether this text already exists at this position.
|
||||
// If so, mark it for inclusion in the next render pass.
|
||||
|
||||
for (var i = 0, position; positions[i]; i++) {
|
||||
position = positions[i];
|
||||
if (position.x === x && position.y === y && position.text === text) {
|
||||
position.active = true;
|
||||
// update the transforms
|
||||
updateTransforms(position.element, transforms);
|
||||
|
||||
return;
|
||||
} else if (position.active === false) {
|
||||
position.active = true;
|
||||
position.text = text;
|
||||
if (text.indexOf('<br>') !== -1) {
|
||||
y -= 0.25 * info.height;
|
||||
addTspanElements(text, position.element, x);
|
||||
} else {
|
||||
position.element.textContent = text;
|
||||
}
|
||||
position.element.setAttributeNS(null, 'x', x);
|
||||
position.element.setAttributeNS(null, 'y', y);
|
||||
position.x = x;
|
||||
position.y = y;
|
||||
// update the transforms
|
||||
updateTransforms(position.element, transforms);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the text doesn't exist at this position, create a new entry
|
||||
|
||||
// For the very first position we'll re-use the original element,
|
||||
// while for subsequent ones we'll clone it.
|
||||
|
||||
position = {
|
||||
active: true,
|
||||
rendered: false,
|
||||
element: positions.length ? info.element.cloneNode() : info.element,
|
||||
text: text,
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
|
||||
positions.push(position);
|
||||
|
||||
if (text.indexOf('<br>') !== -1) {
|
||||
y -= 0.25 * info.height;
|
||||
addTspanElements(text, position.element, x);
|
||||
} else {
|
||||
position.element.textContent = text;
|
||||
}
|
||||
|
||||
// Move the element to its final position within the container
|
||||
position.element.setAttributeNS(null, 'x', x);
|
||||
position.element.setAttributeNS(null, 'y', y);
|
||||
position.element.style.textAlign = halign;
|
||||
// update the transforms
|
||||
updateTransforms(position.element, transforms);
|
||||
};
|
||||
|
||||
var addTspanElements = function(text, element, x) {
|
||||
var lines = text.split('<br>'),
|
||||
tspan, i, offset;
|
||||
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
if (!element.childNodes[i]) {
|
||||
tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||
element.appendChild(tspan);
|
||||
} else {
|
||||
tspan = element.childNodes[i];
|
||||
}
|
||||
tspan.textContent = lines[i];
|
||||
offset = i * 1 + 'em';
|
||||
tspan.setAttributeNS(null, 'dy', offset);
|
||||
tspan.setAttributeNS(null, 'x', x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- removeText (layer, x, y, text, font, angle)
|
||||
|
||||
The function removes one or more text strings from the canvas text overlay.
|
||||
If no parameters are given, all text within the layer is removed.
|
||||
|
||||
Note that the text is not immediately removed; it is simply marked as
|
||||
inactive, which will result in its removal on the next render pass.
|
||||
This avoids the performance penalty for 'clear and redraw' behavior,
|
||||
where we potentially get rid of all text on a layer, but will likely
|
||||
add back most or all of it later, as when redrawing axes, for example.
|
||||
|
||||
The layer is a string of space-separated CSS classes uniquely
|
||||
identifying the layer containing this text. The following parameter are
|
||||
X and Y coordinate of the text.
|
||||
Text is the string to remove, while the font is either a string of space-separated CSS
|
||||
classes or a font-spec object, defining the text's font and style.
|
||||
*/
|
||||
Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
|
||||
var info, htmlYCoord;
|
||||
if (text == null) {
|
||||
var layerCache = this._textCache[layer];
|
||||
if (layerCache != null) {
|
||||
for (var styleKey in layerCache) {
|
||||
if (hasOwnProperty.call(layerCache, styleKey)) {
|
||||
var styleCache = layerCache[styleKey];
|
||||
for (var key in styleCache) {
|
||||
if (hasOwnProperty.call(styleCache, key)) {
|
||||
var positions = styleCache[key].positions;
|
||||
positions.forEach(function(position) {
|
||||
position.active = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info = this.getTextInfo(layer, text, font, angle);
|
||||
positions = info.positions;
|
||||
positions.forEach(function(position) {
|
||||
htmlYCoord = y + 0.75 * info.height;
|
||||
if (position.x === x && position.y === htmlYCoord && position.text === text) {
|
||||
position.active = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
- clearCache()
|
||||
|
||||
Clears the cache used to speed up the text size measurements.
|
||||
As an (unfortunate) side effect all text within the text Layer is removed.
|
||||
Use this function before plot.setupGrid() and plot.draw() if the plot just
|
||||
became visible or the styles changed.
|
||||
*/
|
||||
Canvas.prototype.clearCache = function() {
|
||||
var cache = this._textCache;
|
||||
for (var layerKey in cache) {
|
||||
if (hasOwnProperty.call(cache, layerKey)) {
|
||||
var layer = this.getSVGLayer(layerKey);
|
||||
while (layer.firstChild) {
|
||||
layer.removeChild(layer.firstChild);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this._textCache = {};
|
||||
};
|
||||
|
||||
function generateKey(text) {
|
||||
return text.replace(/0|1|2|3|4|5|6|7|8|9/g, '0');
|
||||
}
|
||||
|
||||
if (!window.Flot) {
|
||||
window.Flot = {};
|
||||
}
|
||||
|
||||
window.Flot.Canvas = Canvas;
|
||||
})(jQuery);
|
||||
@@ -1,199 +0,0 @@
|
||||
/* Plugin for jQuery for working with colors.
|
||||
*
|
||||
* Version 1.1.
|
||||
*
|
||||
* Inspiration from jQuery color animation plugin by John Resig.
|
||||
*
|
||||
* Released under the MIT license by Ole Laursen, October 2009.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
|
||||
* var c = $.color.extract($("#mydiv"), 'background-color');
|
||||
* console.log(c.r, c.g, c.b, c.a);
|
||||
* $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
|
||||
*
|
||||
* Note that .scale() and .add() return the same modified object
|
||||
* instead of making a new one.
|
||||
*
|
||||
* V. 1.1: Fix error handling so e.g. parsing an empty string does
|
||||
* produce a color rather than just crashing.
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
$.color = {};
|
||||
|
||||
// construct color object with some convenient chainable helpers
|
||||
$.color.make = function (r, g, b, a) {
|
||||
var o = {};
|
||||
o.r = r || 0;
|
||||
o.g = g || 0;
|
||||
o.b = b || 0;
|
||||
o.a = a != null ? a : 1;
|
||||
|
||||
o.add = function (c, d) {
|
||||
for (var i = 0; i < c.length; ++i) {
|
||||
o[c.charAt(i)] += d;
|
||||
}
|
||||
|
||||
return o.normalize();
|
||||
};
|
||||
|
||||
o.scale = function (c, f) {
|
||||
for (var i = 0; i < c.length; ++i) {
|
||||
o[c.charAt(i)] *= f;
|
||||
}
|
||||
|
||||
return o.normalize();
|
||||
};
|
||||
|
||||
o.toString = function () {
|
||||
if (o.a >= 1.0) {
|
||||
return "rgb(" + [o.r, o.g, o.b].join(",") + ")";
|
||||
} else {
|
||||
return "rgba(" + [o.r, o.g, o.b, o.a].join(",") + ")";
|
||||
}
|
||||
};
|
||||
|
||||
o.normalize = function () {
|
||||
function clamp(min, value, max) {
|
||||
return value < min ? min : (value > max ? max : value);
|
||||
}
|
||||
|
||||
o.r = clamp(0, parseInt(o.r), 255);
|
||||
o.g = clamp(0, parseInt(o.g), 255);
|
||||
o.b = clamp(0, parseInt(o.b), 255);
|
||||
o.a = clamp(0, o.a, 1);
|
||||
return o;
|
||||
};
|
||||
|
||||
o.clone = function () {
|
||||
return $.color.make(o.r, o.b, o.g, o.a);
|
||||
};
|
||||
|
||||
return o.normalize();
|
||||
}
|
||||
|
||||
// extract CSS color property from element, going up in the DOM
|
||||
// if it's "transparent"
|
||||
$.color.extract = function (elem, css) {
|
||||
var c;
|
||||
|
||||
do {
|
||||
c = elem.css(css).toLowerCase();
|
||||
// keep going until we find an element that has color, or
|
||||
// we hit the body or root (have no parent)
|
||||
if (c !== '' && c !== 'transparent') {
|
||||
break;
|
||||
}
|
||||
|
||||
elem = elem.parent();
|
||||
} while (elem.length && !$.nodeName(elem.get(0), "body"));
|
||||
|
||||
// catch Safari's way of signalling transparent
|
||||
if (c === "rgba(0, 0, 0, 0)") {
|
||||
c = "transparent";
|
||||
}
|
||||
|
||||
return $.color.parse(c);
|
||||
}
|
||||
|
||||
// parse CSS color string (like "rgb(10, 32, 43)" or "#fff"),
|
||||
// returns color object, if parsing failed, you get black (0, 0,
|
||||
// 0) out
|
||||
$.color.parse = function (str) {
|
||||
var res, m = $.color.make;
|
||||
|
||||
// Look for rgb(num,num,num)
|
||||
res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str);
|
||||
if (res) {
|
||||
return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10));
|
||||
}
|
||||
|
||||
// Look for rgba(num,num,num,num)
|
||||
res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)
|
||||
if (res) {
|
||||
return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4]));
|
||||
}
|
||||
|
||||
// Look for rgb(num%,num%,num%)
|
||||
res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*\)/.exec(str);
|
||||
if (res) {
|
||||
return m(parseFloat(res[1]) * 2.55, parseFloat(res[2]) * 2.55, parseFloat(res[3]) * 2.55);
|
||||
}
|
||||
|
||||
// Look for rgba(num%,num%,num%,num)
|
||||
res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str);
|
||||
if (res) {
|
||||
return m(parseFloat(res[1]) * 2.55, parseFloat(res[2]) * 2.55, parseFloat(res[3]) * 2.55, parseFloat(res[4]));
|
||||
}
|
||||
|
||||
// Look for #a0b1c2
|
||||
res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str);
|
||||
if (res) {
|
||||
return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16));
|
||||
}
|
||||
|
||||
// Look for #fff
|
||||
res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str);
|
||||
if (res) {
|
||||
return m(parseInt(res[1] + res[1], 16), parseInt(res[2] + res[2], 16), parseInt(res[3] + res[3], 16));
|
||||
}
|
||||
|
||||
// Otherwise, we're most likely dealing with a named color
|
||||
var name = $.trim(str).toLowerCase();
|
||||
if (name === "transparent") {
|
||||
return m(255, 255, 255, 0);
|
||||
} else {
|
||||
// default to black
|
||||
res = lookupColors[name] || [0, 0, 0];
|
||||
return m(res[0], res[1], res[2]);
|
||||
}
|
||||
}
|
||||
|
||||
var lookupColors = {
|
||||
aqua: [0, 255, 255],
|
||||
azure: [240, 255, 255],
|
||||
beige: [245, 245, 220],
|
||||
black: [0, 0, 0],
|
||||
blue: [0, 0, 255],
|
||||
brown: [165, 42, 42],
|
||||
cyan: [0, 255, 255],
|
||||
darkblue: [0, 0, 139],
|
||||
darkcyan: [0, 139, 139],
|
||||
darkgrey: [169, 169, 169],
|
||||
darkgreen: [0, 100, 0],
|
||||
darkkhaki: [189, 183, 107],
|
||||
darkmagenta: [139, 0, 139],
|
||||
darkolivegreen: [85, 107, 47],
|
||||
darkorange: [255, 140, 0],
|
||||
darkorchid: [153, 50, 204],
|
||||
darkred: [139, 0, 0],
|
||||
darksalmon: [233, 150, 122],
|
||||
darkviolet: [148, 0, 211],
|
||||
fuchsia: [255, 0, 255],
|
||||
gold: [255, 215, 0],
|
||||
green: [0, 128, 0],
|
||||
indigo: [75, 0, 130],
|
||||
khaki: [240, 230, 140],
|
||||
lightblue: [173, 216, 230],
|
||||
lightcyan: [224, 255, 255],
|
||||
lightgreen: [144, 238, 144],
|
||||
lightgrey: [211, 211, 211],
|
||||
lightpink: [255, 182, 193],
|
||||
lightyellow: [255, 255, 224],
|
||||
lime: [0, 255, 0],
|
||||
magenta: [255, 0, 255],
|
||||
maroon: [128, 0, 0],
|
||||
navy: [0, 0, 128],
|
||||
olive: [128, 128, 0],
|
||||
orange: [255, 165, 0],
|
||||
pink: [255, 192, 203],
|
||||
purple: [128, 0, 128],
|
||||
violet: [128, 0, 128],
|
||||
red: [255, 0, 0],
|
||||
silver: [192, 192, 192],
|
||||
white: [255, 255, 255],
|
||||
yellow: [255, 255, 0]
|
||||
};
|
||||
})(jQuery);
|
||||
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
Axis label plugin for flot
|
||||
|
||||
Derived from:
|
||||
Axis Labels Plugin for flot.
|
||||
http://github.com/markrcote/flot-axislabels
|
||||
|
||||
Original code is Copyright (c) 2010 Xuan Luo.
|
||||
Original code was released under the GPLv3 license by Xuan Luo, September 2010.
|
||||
Original code was rereleased under the MIT license by Xuan Luo, April 2012.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
"use strict";
|
||||
|
||||
var options = {
|
||||
axisLabels: {
|
||||
show: true
|
||||
}
|
||||
};
|
||||
|
||||
function AxisLabel(axisName, position, padding, placeholder, axisLabel, surface) {
|
||||
this.axisName = axisName;
|
||||
this.position = position;
|
||||
this.padding = padding;
|
||||
this.placeholder = placeholder;
|
||||
this.axisLabel = axisLabel;
|
||||
this.surface = surface;
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.elem = null;
|
||||
}
|
||||
|
||||
AxisLabel.prototype.calculateSize = function() {
|
||||
var axisId = this.axisName + 'Label',
|
||||
layerId = axisId + 'Layer',
|
||||
className = axisId + ' axisLabels';
|
||||
|
||||
var info = this.surface.getTextInfo(layerId, this.axisLabel, className);
|
||||
this.labelWidth = info.width;
|
||||
this.labelHeight = info.height;
|
||||
|
||||
if (this.position === 'left' || this.position === 'right') {
|
||||
this.width = this.labelHeight + this.padding;
|
||||
this.height = 0;
|
||||
} else {
|
||||
this.width = 0;
|
||||
this.height = this.labelHeight + this.padding;
|
||||
}
|
||||
};
|
||||
|
||||
AxisLabel.prototype.transforms = function(degrees, x, y, svgLayer) {
|
||||
var transforms = [], translate, rotate;
|
||||
if (x !== 0 || y !== 0) {
|
||||
translate = svgLayer.createSVGTransform();
|
||||
translate.setTranslate(x, y);
|
||||
transforms.push(translate);
|
||||
}
|
||||
if (degrees !== 0) {
|
||||
rotate = svgLayer.createSVGTransform();
|
||||
var centerX = Math.round(this.labelWidth / 2),
|
||||
centerY = 0;
|
||||
rotate.setRotate(degrees, centerX, centerY);
|
||||
transforms.push(rotate);
|
||||
}
|
||||
|
||||
return transforms;
|
||||
};
|
||||
|
||||
AxisLabel.prototype.calculateOffsets = function(box) {
|
||||
var offsets = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
degrees: 0
|
||||
};
|
||||
if (this.position === 'bottom') {
|
||||
offsets.x = box.left + box.width / 2 - this.labelWidth / 2;
|
||||
offsets.y = box.top + box.height - this.labelHeight;
|
||||
} else if (this.position === 'top') {
|
||||
offsets.x = box.left + box.width / 2 - this.labelWidth / 2;
|
||||
offsets.y = box.top;
|
||||
} else if (this.position === 'left') {
|
||||
offsets.degrees = -90;
|
||||
offsets.x = box.left - this.labelWidth / 2;
|
||||
offsets.y = box.height / 2 + box.top;
|
||||
} else if (this.position === 'right') {
|
||||
offsets.degrees = 90;
|
||||
offsets.x = box.left + box.width - this.labelWidth / 2;
|
||||
offsets.y = box.height / 2 + box.top;
|
||||
}
|
||||
offsets.x = Math.round(offsets.x);
|
||||
offsets.y = Math.round(offsets.y);
|
||||
|
||||
return offsets;
|
||||
};
|
||||
|
||||
AxisLabel.prototype.cleanup = function() {
|
||||
var axisId = this.axisName + 'Label',
|
||||
layerId = axisId + 'Layer',
|
||||
className = axisId + ' axisLabels';
|
||||
this.surface.removeText(layerId, 0, 0, this.axisLabel, className);
|
||||
};
|
||||
|
||||
AxisLabel.prototype.draw = function(box) {
|
||||
var axisId = this.axisName + 'Label',
|
||||
layerId = axisId + 'Layer',
|
||||
className = axisId + ' axisLabels',
|
||||
offsets = this.calculateOffsets(box),
|
||||
style = {
|
||||
position: 'absolute',
|
||||
bottom: '',
|
||||
right: '',
|
||||
display: 'inline-block',
|
||||
'white-space': 'nowrap'
|
||||
};
|
||||
|
||||
var layer = this.surface.getSVGLayer(layerId);
|
||||
var transforms = this.transforms(offsets.degrees, offsets.x, offsets.y, layer.parentNode);
|
||||
|
||||
this.surface.addText(layerId, 0, 0, this.axisLabel, className, undefined, undefined, undefined, undefined, transforms);
|
||||
this.surface.render();
|
||||
Object.keys(style).forEach(function(key) {
|
||||
layer.style[key] = style[key];
|
||||
});
|
||||
};
|
||||
|
||||
function init(plot) {
|
||||
plot.hooks.processOptions.push(function(plot, options) {
|
||||
if (!options.axisLabels.show) {
|
||||
return;
|
||||
}
|
||||
|
||||
var axisLabels = {};
|
||||
var defaultPadding = 2; // padding between axis and tick labels
|
||||
|
||||
plot.hooks.axisReserveSpace.push(function(plot, axis) {
|
||||
var opts = axis.options;
|
||||
var axisName = axis.direction + axis.n;
|
||||
|
||||
axis.labelHeight += axis.boxPosition.centerY;
|
||||
axis.labelWidth += axis.boxPosition.centerX;
|
||||
|
||||
if (!opts || !opts.axisLabel || !axis.show) {
|
||||
return;
|
||||
}
|
||||
|
||||
var padding = opts.axisLabelPadding === undefined
|
||||
? defaultPadding
|
||||
: opts.axisLabelPadding;
|
||||
|
||||
var axisLabel = axisLabels[axisName];
|
||||
if (!axisLabel) {
|
||||
axisLabel = new AxisLabel(axisName,
|
||||
opts.position, padding,
|
||||
plot.getPlaceholder()[0], opts.axisLabel, plot.getSurface());
|
||||
axisLabels[axisName] = axisLabel;
|
||||
}
|
||||
|
||||
axisLabel.calculateSize();
|
||||
|
||||
// Incrementing the sizes of the tick labels.
|
||||
axis.labelHeight += axisLabel.height;
|
||||
axis.labelWidth += axisLabel.width;
|
||||
});
|
||||
|
||||
// TODO - use the drawAxis hook
|
||||
plot.hooks.draw.push(function(plot, ctx) {
|
||||
$.each(plot.getAxes(), function(flotAxisName, axis) {
|
||||
var opts = axis.options;
|
||||
if (!opts || !opts.axisLabel || !axis.show) {
|
||||
return;
|
||||
}
|
||||
|
||||
var axisName = axis.direction + axis.n;
|
||||
axisLabels[axisName].draw(axis.box);
|
||||
});
|
||||
});
|
||||
|
||||
plot.hooks.shutdown.push(function(plot, eventHolder) {
|
||||
for (var axisName in axisLabels) {
|
||||
axisLabels[axisName].cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'axisLabels',
|
||||
version: '3.0'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,98 +0,0 @@
|
||||
/** ## jquery.flot.browser.js
|
||||
|
||||
This plugin is used to make available some browser-related utility functions.
|
||||
|
||||
### Methods
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
var browser = {
|
||||
/**
|
||||
- getPageXY(e)
|
||||
|
||||
Calculates the pageX and pageY using the screenX, screenY properties of the event
|
||||
and the scrolling of the page. This is needed because the pageX and pageY
|
||||
properties of the event are not correct while running tests in Edge. */
|
||||
getPageXY: function (e) {
|
||||
// This code is inspired from https://stackoverflow.com/a/3464890
|
||||
var doc = document.documentElement,
|
||||
pageX = e.clientX + (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
|
||||
pageY = e.clientY + (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
|
||||
return { X: pageX, Y: pageY };
|
||||
},
|
||||
|
||||
/**
|
||||
- getPixelRatio(context)
|
||||
|
||||
This function returns the current pixel ratio defined by the product of desktop
|
||||
zoom and page zoom.
|
||||
Additional info: https://www.html5rocks.com/en/tutorials/canvas/hidpi/
|
||||
*/
|
||||
getPixelRatio: function(context) {
|
||||
var devicePixelRatio = window.devicePixelRatio || 1,
|
||||
backingStoreRatio =
|
||||
context.webkitBackingStorePixelRatio ||
|
||||
context.mozBackingStorePixelRatio ||
|
||||
context.msBackingStorePixelRatio ||
|
||||
context.oBackingStorePixelRatio ||
|
||||
context.backingStorePixelRatio || 1;
|
||||
return devicePixelRatio / backingStoreRatio;
|
||||
},
|
||||
|
||||
/**
|
||||
- isSafari, isMobileSafari, isOpera, isFirefox, isIE, isEdge, isChrome, isBlink
|
||||
|
||||
This is a collection of functions, used to check if the code is running in a
|
||||
particular browser or Javascript engine.
|
||||
*/
|
||||
isSafari: function() {
|
||||
// *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
|
||||
// Safari 3.0+ "[object HTMLElementConstructor]"
|
||||
return /constructor/i.test(window.top.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window.top['safari'] || (typeof window.top.safari !== 'undefined' && window.top.safari.pushNotification));
|
||||
},
|
||||
|
||||
isMobileSafari: function() {
|
||||
//isMobileSafari adapted from https://stackoverflow.com/questions/3007480/determine-if-user-navigated-from-mobile-safari
|
||||
return navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/);
|
||||
},
|
||||
|
||||
isOpera: function() {
|
||||
// *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
|
||||
//Opera 8.0+
|
||||
return (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
|
||||
},
|
||||
|
||||
isFirefox: function() {
|
||||
// *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
|
||||
// Firefox 1.0+
|
||||
return typeof InstallTrigger !== 'undefined';
|
||||
},
|
||||
|
||||
isIE: function() {
|
||||
// *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
|
||||
// Internet Explorer 6-11
|
||||
return /*@cc_on!@*/false || !!document.documentMode;
|
||||
},
|
||||
|
||||
isEdge: function() {
|
||||
// *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
|
||||
// Edge 20+
|
||||
return !browser.isIE() && !!window.StyleMedia;
|
||||
},
|
||||
|
||||
isChrome: function() {
|
||||
// *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
|
||||
// Chrome 1+
|
||||
return !!window.chrome && !!window.chrome.webstore;
|
||||
},
|
||||
|
||||
isBlink: function() {
|
||||
// *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
|
||||
return (browser.isChrome() || browser.isOpera()) && !!window.CSS;
|
||||
}
|
||||
};
|
||||
|
||||
$.plot.browser = browser;
|
||||
})(jQuery);
|
||||
@@ -1,202 +0,0 @@
|
||||
/* Flot plugin for plotting textual data or categories.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin
|
||||
allows you to plot such a dataset directly.
|
||||
|
||||
To enable it, you must specify mode: "categories" on the axis with the textual
|
||||
labels, e.g.
|
||||
|
||||
$.plot("#placeholder", data, { xaxis: { mode: "categories" } });
|
||||
|
||||
By default, the labels are ordered as they are met in the data series. If you
|
||||
need a different ordering, you can specify "categories" on the axis options
|
||||
and list the categories there:
|
||||
|
||||
xaxis: {
|
||||
mode: "categories",
|
||||
categories: ["February", "March", "April"]
|
||||
}
|
||||
|
||||
If you need to customize the distances between the categories, you can specify
|
||||
"categories" as an object mapping labels to values
|
||||
|
||||
xaxis: {
|
||||
mode: "categories",
|
||||
categories: { "February": 1, "March": 3, "April": 4 }
|
||||
}
|
||||
|
||||
If you don't specify all categories, the remaining categories will be numbered
|
||||
from the max value plus 1 (with a spacing of 1 between each).
|
||||
|
||||
Internally, the plugin works by transforming the input data through an auto-
|
||||
generated mapping where the first category becomes 0, the second 1, etc.
|
||||
Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this
|
||||
is visible in hover and click events that return numbers rather than the
|
||||
category labels). The plugin also overrides the tick generator to spit out the
|
||||
categories as ticks instead of the values.
|
||||
|
||||
If you need to map a value back to its label, the mapping is always accessible
|
||||
as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories.
|
||||
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var options = {
|
||||
xaxis: {
|
||||
categories: null
|
||||
},
|
||||
yaxis: {
|
||||
categories: null
|
||||
}
|
||||
};
|
||||
|
||||
function processRawData(plot, series, data, datapoints) {
|
||||
// if categories are enabled, we need to disable
|
||||
// auto-transformation to numbers so the strings are intact
|
||||
// for later processing
|
||||
|
||||
var xCategories = series.xaxis.options.mode === "categories",
|
||||
yCategories = series.yaxis.options.mode === "categories";
|
||||
|
||||
if (!(xCategories || yCategories)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var format = datapoints.format;
|
||||
|
||||
if (!format) {
|
||||
// FIXME: auto-detection should really not be defined here
|
||||
var s = series;
|
||||
format = [];
|
||||
format.push({ x: true, number: true, required: true, computeRange: true});
|
||||
format.push({ y: true, number: true, required: true, computeRange: true });
|
||||
|
||||
if (s.bars.show || (s.lines.show && s.lines.fill)) {
|
||||
var autoScale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
|
||||
format.push({ y: true, number: true, required: false, defaultValue: 0, computeRange: autoScale });
|
||||
if (s.bars.horizontal) {
|
||||
delete format[format.length - 1].y;
|
||||
format[format.length - 1].x = true;
|
||||
}
|
||||
}
|
||||
|
||||
datapoints.format = format;
|
||||
}
|
||||
|
||||
for (var m = 0; m < format.length; ++m) {
|
||||
if (format[m].x && xCategories) {
|
||||
format[m].number = false;
|
||||
}
|
||||
|
||||
if (format[m].y && yCategories) {
|
||||
format[m].number = false;
|
||||
format[m].computeRange = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getNextIndex(categories) {
|
||||
var index = -1;
|
||||
|
||||
for (var v in categories) {
|
||||
if (categories[v] > index) {
|
||||
index = categories[v];
|
||||
}
|
||||
}
|
||||
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
function categoriesTickGenerator(axis) {
|
||||
var res = [];
|
||||
for (var label in axis.categories) {
|
||||
var v = axis.categories[label];
|
||||
if (v >= axis.min && v <= axis.max) {
|
||||
res.push([v, label]);
|
||||
}
|
||||
}
|
||||
|
||||
res.sort(function (a, b) { return a[0] - b[0]; });
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function setupCategoriesForAxis(series, axis, datapoints) {
|
||||
if (series[axis].options.mode !== "categories") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!series[axis].categories) {
|
||||
// parse options
|
||||
var c = {}, o = series[axis].options.categories || {};
|
||||
if ($.isArray(o)) {
|
||||
for (var i = 0; i < o.length; ++i) {
|
||||
c[o[i]] = i;
|
||||
}
|
||||
} else {
|
||||
for (var v in o) {
|
||||
c[v] = o[v];
|
||||
}
|
||||
}
|
||||
|
||||
series[axis].categories = c;
|
||||
}
|
||||
|
||||
// fix ticks
|
||||
if (!series[axis].options.ticks) {
|
||||
series[axis].options.ticks = categoriesTickGenerator;
|
||||
}
|
||||
|
||||
transformPointsOnAxis(datapoints, axis, series[axis].categories);
|
||||
}
|
||||
|
||||
function transformPointsOnAxis(datapoints, axis, categories) {
|
||||
// go through the points, transforming them
|
||||
var points = datapoints.points,
|
||||
ps = datapoints.pointsize,
|
||||
format = datapoints.format,
|
||||
formatColumn = axis.charAt(0),
|
||||
index = getNextIndex(categories);
|
||||
|
||||
for (var i = 0; i < points.length; i += ps) {
|
||||
if (points[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var m = 0; m < ps; ++m) {
|
||||
var val = points[i + m];
|
||||
|
||||
if (val == null || !format[m][formatColumn]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(val in categories)) {
|
||||
categories[val] = index;
|
||||
++index;
|
||||
}
|
||||
|
||||
points[i + m] = categories[val];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processDatapoints(plot, series, datapoints) {
|
||||
setupCategoriesForAxis(series, "xaxis", datapoints);
|
||||
setupCategoriesForAxis(series, "yaxis", datapoints);
|
||||
}
|
||||
|
||||
function init(plot) {
|
||||
plot.hooks.processRawData.push(processRawData);
|
||||
plot.hooks.processDatapoints.push(processDatapoints);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'categories',
|
||||
version: '1.0'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,330 +0,0 @@
|
||||
/** ## jquery.flot.composeImages.js
|
||||
|
||||
This plugin is used to expose a function used to overlap several canvases and
|
||||
SVGs, for the purpose of creating a snaphot out of them.
|
||||
|
||||
### When composeImages is used:
|
||||
When multiple canvases and SVGs have to be overlapped into a single image
|
||||
and their offset on the page, must be preserved.
|
||||
|
||||
### Where can be used:
|
||||
In creating a downloadable snapshot of the plots, axes, cursors etc of a graph.
|
||||
|
||||
### How it works:
|
||||
The entry point is composeImages function. It expects an array of objects,
|
||||
which should be either canvases or SVGs (or a mix). It does a prevalidation
|
||||
of them, by verifying if they will be usable or not, later in the flow.
|
||||
After selecting only usable sources, it passes them to getGenerateTempImg
|
||||
function, which generates temporary images out of them. This function
|
||||
expects that some of the passed sources (canvas or SVG) may still have
|
||||
problems being converted to an image and makes sure the promises system,
|
||||
used by composeImages function, moves forward. As an example, SVGs with
|
||||
missing information from header or with unsupported content, may lead to
|
||||
failure in generating the temporary image. Temporary images are required
|
||||
mostly on extracting content from SVGs, but this is also where the x/y
|
||||
offsets are extracted for each image which will be added. For SVGs in
|
||||
particular, their CSS rules have to be applied.
|
||||
After all temporary images are generated, they are overlapped using
|
||||
getExecuteImgComposition function. This is where the destination canvas
|
||||
is set to the proper dimensions. It is then output by composeImages.
|
||||
This function returns a promise, which can be used to wait for the whole
|
||||
composition process. It requires to be asynchronous, because this is how
|
||||
temporary images load their data.
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
"use strict";
|
||||
const GENERALFAILURECALLBACKERROR = -100; //simply a negative number
|
||||
const SUCCESSFULIMAGEPREPARATION = 0;
|
||||
const EMPTYARRAYOFIMAGESOURCES = -1;
|
||||
const NEGATIVEIMAGESIZE = -2;
|
||||
var pixelRatio = 1;
|
||||
var browser = $.plot.browser;
|
||||
var getPixelRatio = browser.getPixelRatio;
|
||||
|
||||
function composeImages(canvasOrSvgSources, destinationCanvas) {
|
||||
var validCanvasOrSvgSources = canvasOrSvgSources.filter(isValidSource);
|
||||
pixelRatio = getPixelRatio(destinationCanvas.getContext('2d'));
|
||||
|
||||
var allImgCompositionPromises = validCanvasOrSvgSources.map(function(validCanvasOrSvgSource) {
|
||||
var tempImg = new Image();
|
||||
var currentPromise = new Promise(getGenerateTempImg(tempImg, validCanvasOrSvgSource));
|
||||
return currentPromise;
|
||||
});
|
||||
|
||||
var lastPromise = Promise.all(allImgCompositionPromises).then(getExecuteImgComposition(destinationCanvas), failureCallback);
|
||||
return lastPromise;
|
||||
}
|
||||
|
||||
function isValidSource(canvasOrSvgSource) {
|
||||
var isValidFromCanvas = true;
|
||||
var isValidFromContent = true;
|
||||
if ((canvasOrSvgSource === null) || (canvasOrSvgSource === undefined)) {
|
||||
isValidFromContent = false;
|
||||
} else {
|
||||
if (canvasOrSvgSource.tagName === 'CANVAS') {
|
||||
if ((canvasOrSvgSource.getBoundingClientRect().right === canvasOrSvgSource.getBoundingClientRect().left) ||
|
||||
(canvasOrSvgSource.getBoundingClientRect().bottom === canvasOrSvgSource.getBoundingClientRect().top)) {
|
||||
isValidFromCanvas = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return isValidFromContent && isValidFromCanvas && (window.getComputedStyle(canvasOrSvgSource).visibility === 'visible');
|
||||
}
|
||||
|
||||
function getGenerateTempImg(tempImg, canvasOrSvgSource) {
|
||||
tempImg.sourceDescription = '<info className="' + canvasOrSvgSource.className + '" tagName="' + canvasOrSvgSource.tagName + '" id="' + canvasOrSvgSource.id + '">';
|
||||
tempImg.sourceComponent = canvasOrSvgSource;
|
||||
|
||||
return function doGenerateTempImg(successCallbackFunc, failureCallbackFunc) {
|
||||
tempImg.onload = function(evt) {
|
||||
tempImg.successfullyLoaded = true;
|
||||
successCallbackFunc(tempImg);
|
||||
};
|
||||
|
||||
tempImg.onabort = function(evt) {
|
||||
tempImg.successfullyLoaded = false;
|
||||
console.log('Can\'t generate temp image from ' + tempImg.sourceDescription + '. It is possible that it is missing some properties or its content is not supported by this browser. Source component:', tempImg.sourceComponent);
|
||||
successCallbackFunc(tempImg); //call successCallback, to allow snapshot of all working images
|
||||
};
|
||||
|
||||
tempImg.onerror = function(evt) {
|
||||
tempImg.successfullyLoaded = false;
|
||||
console.log('Can\'t generate temp image from ' + tempImg.sourceDescription + '. It is possible that it is missing some properties or its content is not supported by this browser. Source component:', tempImg.sourceComponent);
|
||||
successCallbackFunc(tempImg); //call successCallback, to allow snapshot of all working images
|
||||
};
|
||||
|
||||
generateTempImageFromCanvasOrSvg(canvasOrSvgSource, tempImg);
|
||||
};
|
||||
}
|
||||
|
||||
function getExecuteImgComposition(destinationCanvas) {
|
||||
return function executeImgComposition(tempImgs) {
|
||||
var compositionResult = copyImgsToCanvas(tempImgs, destinationCanvas);
|
||||
return compositionResult;
|
||||
};
|
||||
}
|
||||
|
||||
function copyCanvasToImg(canvas, img) {
|
||||
img.src = canvas.toDataURL('image/png');
|
||||
}
|
||||
|
||||
function getCSSRules(document) {
|
||||
var styleSheets = document.styleSheets,
|
||||
rulesList = [];
|
||||
for (var i = 0; i < styleSheets.length; i++) {
|
||||
// CORS requests for style sheets throw and an exception on Chrome > 64
|
||||
try {
|
||||
// in Chrome, the external CSS files are empty when the page is directly loaded from disk
|
||||
var rules = styleSheets[i].cssRules || [];
|
||||
for (var j = 0; j < rules.length; j++) {
|
||||
var rule = rules[j];
|
||||
rulesList.push(rule.cssText);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Failed to get some css rules');
|
||||
}
|
||||
}
|
||||
return rulesList;
|
||||
}
|
||||
|
||||
function embedCSSRulesInSVG(rules, svg) {
|
||||
var text = [
|
||||
'<svg class="snapshot ' + svg.classList + '" width="' + svg.width.baseVal.value * pixelRatio + '" height="' + svg.height.baseVal.value * pixelRatio + '" viewBox="0 0 ' + svg.width.baseVal.value + ' ' + svg.height.baseVal.value + '" xmlns="http://www.w3.org/2000/svg">',
|
||||
'<style>',
|
||||
'/* <![CDATA[ */',
|
||||
rules.join('\n'),
|
||||
'/* ]]> */',
|
||||
'</style>',
|
||||
svg.innerHTML,
|
||||
'</svg>'
|
||||
].join('\n');
|
||||
return text;
|
||||
}
|
||||
|
||||
function copySVGToImgMostBrowsers(svg, img) {
|
||||
var rules = getCSSRules(document),
|
||||
source = embedCSSRulesInSVG(rules, svg);
|
||||
|
||||
source = patchSVGSource(source);
|
||||
|
||||
var blob = new Blob([source], {type: "image/svg+xml;charset=utf-8"}),
|
||||
domURL = self.URL || self.webkitURL || self,
|
||||
url = domURL.createObjectURL(blob);
|
||||
img.src = url;
|
||||
}
|
||||
|
||||
function copySVGToImgSafari(svg, img) {
|
||||
// Use this method to convert a string buffer array to a binary string.
|
||||
// Do so by breaking up large strings into smaller substrings; this is necessary to avoid the
|
||||
// "maximum call stack size exceeded" exception that can happen when calling 'String.fromCharCode.apply'
|
||||
// with a very long array.
|
||||
function buildBinaryString (arrayBuffer) {
|
||||
var binaryString = "";
|
||||
const utf8Array = new Uint8Array(arrayBuffer);
|
||||
const blockSize = 16384;
|
||||
for (var i = 0; i < utf8Array.length; i = i + blockSize) {
|
||||
const binarySubString = String.fromCharCode.apply(null, utf8Array.subarray(i, i + blockSize));
|
||||
binaryString = binaryString + binarySubString;
|
||||
}
|
||||
return binaryString;
|
||||
};
|
||||
|
||||
var rules = getCSSRules(document),
|
||||
source = embedCSSRulesInSVG(rules, svg),
|
||||
data,
|
||||
utf8BinaryString;
|
||||
|
||||
source = patchSVGSource(source);
|
||||
|
||||
// Encode the string as UTF-8 and convert it to a binary string. The UTF-8 encoding is required to
|
||||
// capture unicode characters correctly.
|
||||
utf8BinaryString = buildBinaryString(new (TextEncoder || TextEncoderLite)('utf-8').encode(source));
|
||||
|
||||
data = "data:image/svg+xml;base64," + btoa(utf8BinaryString);
|
||||
img.src = data;
|
||||
}
|
||||
|
||||
function patchSVGSource(svgSource) {
|
||||
var source = '';
|
||||
//add name spaces.
|
||||
if (!svgSource.match(/^<svg[^>]+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/)) {
|
||||
source = svgSource.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
|
||||
}
|
||||
if (!svgSource.match(/^<svg[^>]+"http:\/\/www\.w3\.org\/1999\/xlink"/)) {
|
||||
source = svgSource.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
|
||||
}
|
||||
|
||||
//add xml declaration
|
||||
return '<?xml version="1.0" standalone="no"?>\r\n' + source;
|
||||
}
|
||||
|
||||
function copySVGToImg(svg, img) {
|
||||
if (browser.isSafari() || browser.isMobileSafari()) {
|
||||
copySVGToImgSafari(svg, img);
|
||||
} else {
|
||||
copySVGToImgMostBrowsers(svg, img);
|
||||
}
|
||||
}
|
||||
|
||||
function adaptDestSizeToZoom(destinationCanvas, sources) {
|
||||
function containsSVGs(source) {
|
||||
return source.srcImgTagName === 'svg';
|
||||
}
|
||||
|
||||
if (sources.find(containsSVGs) !== undefined) {
|
||||
if (pixelRatio < 1) {
|
||||
destinationCanvas.width = destinationCanvas.width * pixelRatio;
|
||||
destinationCanvas.height = destinationCanvas.height * pixelRatio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prepareImagesToBeComposed(sources, destination) {
|
||||
var result = SUCCESSFULIMAGEPREPARATION;
|
||||
if (sources.length === 0) {
|
||||
result = EMPTYARRAYOFIMAGESOURCES; //nothing to do if called without sources
|
||||
} else {
|
||||
var minX = sources[0].genLeft;
|
||||
var minY = sources[0].genTop;
|
||||
var maxX = sources[0].genRight;
|
||||
var maxY = sources[0].genBottom;
|
||||
var i = 0;
|
||||
|
||||
for (i = 1; i < sources.length; i++) {
|
||||
if (minX > sources[i].genLeft) {
|
||||
minX = sources[i].genLeft;
|
||||
}
|
||||
|
||||
if (minY > sources[i].genTop) {
|
||||
minY = sources[i].genTop;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 1; i < sources.length; i++) {
|
||||
if (maxX < sources[i].genRight) {
|
||||
maxX = sources[i].genRight;
|
||||
}
|
||||
|
||||
if (maxY < sources[i].genBottom) {
|
||||
maxY = sources[i].genBottom;
|
||||
}
|
||||
}
|
||||
|
||||
if ((maxX - minX <= 0) || (maxY - minY <= 0)) {
|
||||
result = NEGATIVEIMAGESIZE; //this might occur on hidden images
|
||||
} else {
|
||||
destination.width = Math.round(maxX - minX);
|
||||
destination.height = Math.round(maxY - minY);
|
||||
|
||||
for (i = 0; i < sources.length; i++) {
|
||||
sources[i].xCompOffset = sources[i].genLeft - minX;
|
||||
sources[i].yCompOffset = sources[i].genTop - minY;
|
||||
}
|
||||
|
||||
adaptDestSizeToZoom(destination, sources);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function copyImgsToCanvas(sources, destination) {
|
||||
var prepareImagesResult = prepareImagesToBeComposed(sources, destination);
|
||||
if (prepareImagesResult === SUCCESSFULIMAGEPREPARATION) {
|
||||
var destinationCtx = destination.getContext('2d');
|
||||
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
if (sources[i].successfullyLoaded === true) {
|
||||
destinationCtx.drawImage(sources[i], sources[i].xCompOffset * pixelRatio, sources[i].yCompOffset * pixelRatio);
|
||||
}
|
||||
}
|
||||
}
|
||||
return prepareImagesResult;
|
||||
}
|
||||
|
||||
function adnotateDestImgWithBoundingClientRect(srcCanvasOrSvg, destImg) {
|
||||
destImg.genLeft = srcCanvasOrSvg.getBoundingClientRect().left;
|
||||
destImg.genTop = srcCanvasOrSvg.getBoundingClientRect().top;
|
||||
|
||||
if (srcCanvasOrSvg.tagName === 'CANVAS') {
|
||||
destImg.genRight = destImg.genLeft + srcCanvasOrSvg.width;
|
||||
destImg.genBottom = destImg.genTop + srcCanvasOrSvg.height;
|
||||
}
|
||||
|
||||
if (srcCanvasOrSvg.tagName === 'svg') {
|
||||
destImg.genRight = srcCanvasOrSvg.getBoundingClientRect().right;
|
||||
destImg.genBottom = srcCanvasOrSvg.getBoundingClientRect().bottom;
|
||||
}
|
||||
}
|
||||
|
||||
function generateTempImageFromCanvasOrSvg(srcCanvasOrSvg, destImg) {
|
||||
if (srcCanvasOrSvg.tagName === 'CANVAS') {
|
||||
copyCanvasToImg(srcCanvasOrSvg, destImg);
|
||||
}
|
||||
|
||||
if (srcCanvasOrSvg.tagName === 'svg') {
|
||||
copySVGToImg(srcCanvasOrSvg, destImg);
|
||||
}
|
||||
|
||||
destImg.srcImgTagName = srcCanvasOrSvg.tagName;
|
||||
adnotateDestImgWithBoundingClientRect(srcCanvasOrSvg, destImg);
|
||||
}
|
||||
|
||||
function failureCallback() {
|
||||
return GENERALFAILURECALLBACKERROR;
|
||||
}
|
||||
|
||||
// used for testing
|
||||
$.plot.composeImages = composeImages;
|
||||
|
||||
function init(plot) {
|
||||
// used to extend the public API of the plot
|
||||
plot.composeImages = composeImages;
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
name: 'composeImages',
|
||||
version: '1.0'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,202 +0,0 @@
|
||||
/* Flot plugin for showing crosshairs when the mouse hovers over the plot.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The plugin supports these options:
|
||||
|
||||
crosshair: {
|
||||
mode: null or "x" or "y" or "xy"
|
||||
color: color
|
||||
lineWidth: number
|
||||
}
|
||||
|
||||
Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
|
||||
crosshair that lets you trace the values on the x axis, "y" enables a
|
||||
horizontal crosshair and "xy" enables them both. "color" is the color of the
|
||||
crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
|
||||
the drawn lines (default is 1).
|
||||
|
||||
The plugin also adds four public methods:
|
||||
|
||||
- setCrosshair( pos )
|
||||
|
||||
Set the position of the crosshair. Note that this is cleared if the user
|
||||
moves the mouse. "pos" is in coordinates of the plot and should be on the
|
||||
form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
|
||||
axes), which is coincidentally the same format as what you get from a
|
||||
"plothover" event. If "pos" is null, the crosshair is cleared.
|
||||
|
||||
- clearCrosshair()
|
||||
|
||||
Clear the crosshair.
|
||||
|
||||
- lockCrosshair(pos)
|
||||
|
||||
Cause the crosshair to lock to the current location, no longer updating if
|
||||
the user moves the mouse. Optionally supply a position (passed on to
|
||||
setCrosshair()) to move it to.
|
||||
|
||||
Example usage:
|
||||
|
||||
var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
|
||||
$("#graph").bind( "plothover", function ( evt, position, item ) {
|
||||
if ( item ) {
|
||||
// Lock the crosshair to the data point being hovered
|
||||
myFlot.lockCrosshair({
|
||||
x: item.datapoint[ 0 ],
|
||||
y: item.datapoint[ 1 ]
|
||||
});
|
||||
} else {
|
||||
// Return normal crosshair operation
|
||||
myFlot.unlockCrosshair();
|
||||
}
|
||||
});
|
||||
|
||||
- unlockCrosshair()
|
||||
|
||||
Free the crosshair to move again after locking it.
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var options = {
|
||||
crosshair: {
|
||||
mode: null, // one of null, "x", "y" or "xy",
|
||||
color: "rgba(170, 0, 0, 0.80)",
|
||||
lineWidth: 1
|
||||
}
|
||||
};
|
||||
|
||||
function init(plot) {
|
||||
// position of crosshair in pixels
|
||||
var crosshair = {x: -1, y: -1, locked: false, highlighted: false};
|
||||
|
||||
plot.setCrosshair = function setCrosshair(pos) {
|
||||
if (!pos) {
|
||||
crosshair.x = -1;
|
||||
} else {
|
||||
var o = plot.p2c(pos);
|
||||
crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
|
||||
crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
|
||||
}
|
||||
|
||||
plot.triggerRedrawOverlay();
|
||||
};
|
||||
|
||||
plot.clearCrosshair = plot.setCrosshair; // passes null for pos
|
||||
|
||||
plot.lockCrosshair = function lockCrosshair(pos) {
|
||||
if (pos) {
|
||||
plot.setCrosshair(pos);
|
||||
}
|
||||
|
||||
crosshair.locked = true;
|
||||
};
|
||||
|
||||
plot.unlockCrosshair = function unlockCrosshair() {
|
||||
crosshair.locked = false;
|
||||
crosshair.rect = null;
|
||||
};
|
||||
|
||||
function onMouseOut(e) {
|
||||
if (crosshair.locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (crosshair.x !== -1) {
|
||||
crosshair.x = -1;
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseMove(e) {
|
||||
var offset = plot.offset();
|
||||
if (crosshair.locked) {
|
||||
var mouseX = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
|
||||
var mouseY = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
|
||||
|
||||
if ((mouseX > crosshair.x - 4) && (mouseX < crosshair.x + 4) && (mouseY > crosshair.y - 4) && (mouseY < crosshair.y + 4)) {
|
||||
if (!crosshair.highlighted) {
|
||||
crosshair.highlighted = true;
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
} else {
|
||||
if (crosshair.highlighted) {
|
||||
crosshair.highlighted = false;
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (plot.getSelection && plot.getSelection()) {
|
||||
crosshair.x = -1; // hide the crosshair while selecting
|
||||
return;
|
||||
}
|
||||
|
||||
crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
|
||||
crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
|
||||
plot.hooks.bindEvents.push(function (plot, eventHolder) {
|
||||
if (!plot.getOptions().crosshair.mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventHolder.mouseout(onMouseOut);
|
||||
eventHolder.mousemove(onMouseMove);
|
||||
});
|
||||
|
||||
plot.hooks.drawOverlay.push(function (plot, ctx) {
|
||||
var c = plot.getOptions().crosshair;
|
||||
if (!c.mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
var plotOffset = plot.getPlotOffset();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(plotOffset.left, plotOffset.top);
|
||||
|
||||
if (crosshair.x !== -1) {
|
||||
var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;
|
||||
|
||||
ctx.strokeStyle = c.color;
|
||||
ctx.lineWidth = c.lineWidth;
|
||||
ctx.lineJoin = "round";
|
||||
|
||||
ctx.beginPath();
|
||||
if (c.mode.indexOf("x") !== -1) {
|
||||
var drawX = Math.floor(crosshair.x) + adj;
|
||||
ctx.moveTo(drawX, 0);
|
||||
ctx.lineTo(drawX, plot.height());
|
||||
}
|
||||
if (c.mode.indexOf("y") !== -1) {
|
||||
var drawY = Math.floor(crosshair.y) + adj;
|
||||
ctx.moveTo(0, drawY);
|
||||
ctx.lineTo(plot.width(), drawY);
|
||||
}
|
||||
if (crosshair.locked) {
|
||||
if (crosshair.highlighted) ctx.fillStyle = 'orange';
|
||||
else ctx.fillStyle = c.color;
|
||||
ctx.fillRect(Math.floor(crosshair.x) + adj - 4, Math.floor(crosshair.y) + adj - 4, 8, 8);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
});
|
||||
|
||||
plot.hooks.shutdown.push(function (plot, eventHolder) {
|
||||
eventHolder.unbind("mouseout", onMouseOut);
|
||||
eventHolder.unbind("mousemove", onMouseMove);
|
||||
});
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'crosshair',
|
||||
version: '1.0'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,663 +0,0 @@
|
||||
/**
|
||||
## jquery.flot.drawSeries.js
|
||||
|
||||
This plugin is used by flot for drawing lines, plots, bars or area.
|
||||
|
||||
### Public methods
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
"use strict";
|
||||
|
||||
function DrawSeries() {
|
||||
function plotLine(datapoints, xoffset, yoffset, axisx, axisy, ctx, steps) {
|
||||
var points = datapoints.points,
|
||||
ps = datapoints.pointsize,
|
||||
prevx = null,
|
||||
prevy = null;
|
||||
var x1 = 0.0,
|
||||
y1 = 0.0,
|
||||
x2 = 0.0,
|
||||
y2 = 0.0,
|
||||
mx = null,
|
||||
my = null,
|
||||
i = 0;
|
||||
|
||||
ctx.beginPath();
|
||||
for (i = ps; i < points.length; i += ps) {
|
||||
x1 = points[i - ps];
|
||||
y1 = points[i - ps + 1];
|
||||
x2 = points[i];
|
||||
y2 = points[i + 1];
|
||||
|
||||
if (x1 === null || x2 === null) {
|
||||
mx = null;
|
||||
my = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isNaN(x1) || isNaN(x2) || isNaN(y1) || isNaN(y2)) {
|
||||
prevx = null;
|
||||
prevy = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(steps){
|
||||
if (mx !== null && my !== null) {
|
||||
// if middle point exists, transfer p2 -> p1 and p1 -> mp
|
||||
x2 = x1;
|
||||
y2 = y1;
|
||||
x1 = mx;
|
||||
y1 = my;
|
||||
|
||||
// 'remove' middle point
|
||||
mx = null;
|
||||
my = null;
|
||||
|
||||
// subtract pointsize from i to have current point p1 handled again
|
||||
i -= ps;
|
||||
} else if (y1 !== y2 && x1 !== x2) {
|
||||
// create a middle point
|
||||
y2 = y1;
|
||||
mx = x2;
|
||||
my = y1;
|
||||
}
|
||||
}
|
||||
|
||||
// clip with ymin
|
||||
if (y1 <= y2 && y1 < axisy.min) {
|
||||
if (y2 < axisy.min) {
|
||||
// line segment is outside
|
||||
continue;
|
||||
}
|
||||
// compute new intersection point
|
||||
x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
|
||||
y1 = axisy.min;
|
||||
} else if (y2 <= y1 && y2 < axisy.min) {
|
||||
if (y1 < axisy.min) {
|
||||
continue;
|
||||
}
|
||||
|
||||
x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
|
||||
y2 = axisy.min;
|
||||
}
|
||||
|
||||
// clip with ymax
|
||||
if (y1 >= y2 && y1 > axisy.max) {
|
||||
if (y2 > axisy.max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
|
||||
y1 = axisy.max;
|
||||
} else if (y2 >= y1 && y2 > axisy.max) {
|
||||
if (y1 > axisy.max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
|
||||
y2 = axisy.max;
|
||||
}
|
||||
|
||||
// clip with xmin
|
||||
if (x1 <= x2 && x1 < axisx.min) {
|
||||
if (x2 < axisx.min) {
|
||||
continue;
|
||||
}
|
||||
|
||||
y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
|
||||
x1 = axisx.min;
|
||||
} else if (x2 <= x1 && x2 < axisx.min) {
|
||||
if (x1 < axisx.min) {
|
||||
continue;
|
||||
}
|
||||
|
||||
y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
|
||||
x2 = axisx.min;
|
||||
}
|
||||
|
||||
// clip with xmax
|
||||
if (x1 >= x2 && x1 > axisx.max) {
|
||||
if (x2 > axisx.max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
|
||||
x1 = axisx.max;
|
||||
} else if (x2 >= x1 && x2 > axisx.max) {
|
||||
if (x1 > axisx.max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
|
||||
x2 = axisx.max;
|
||||
}
|
||||
|
||||
if (x1 !== prevx || y1 !== prevy) {
|
||||
ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
|
||||
}
|
||||
|
||||
prevx = x2;
|
||||
prevy = y2;
|
||||
ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function plotLineArea(datapoints, axisx, axisy, fillTowards, ctx, steps) {
|
||||
var points = datapoints.points,
|
||||
ps = datapoints.pointsize,
|
||||
bottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min,
|
||||
i = 0,
|
||||
ypos = 1,
|
||||
areaOpen = false,
|
||||
segmentStart = 0,
|
||||
segmentEnd = 0,
|
||||
mx = null,
|
||||
my = null;
|
||||
|
||||
// we process each segment in two turns, first forward
|
||||
// direction to sketch out top, then once we hit the
|
||||
// end we go backwards to sketch the bottom
|
||||
while (true) {
|
||||
if (ps > 0 && i > points.length + ps) {
|
||||
break;
|
||||
}
|
||||
|
||||
i += ps; // ps is negative if going backwards
|
||||
|
||||
var x1 = points[i - ps],
|
||||
y1 = points[i - ps + ypos],
|
||||
x2 = points[i],
|
||||
y2 = points[i + ypos];
|
||||
|
||||
if (ps === -2) {
|
||||
/* going backwards and no value for the bottom provided in the series*/
|
||||
y1 = y2 = bottom;
|
||||
}
|
||||
|
||||
if (areaOpen) {
|
||||
if (ps > 0 && x1 != null && x2 == null) {
|
||||
// at turning point
|
||||
segmentEnd = i;
|
||||
ps = -ps;
|
||||
ypos = 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ps < 0 && i === segmentStart + ps) {
|
||||
// done with the reverse sweep
|
||||
ctx.fill();
|
||||
areaOpen = false;
|
||||
ps = -ps;
|
||||
ypos = 1;
|
||||
i = segmentStart = segmentEnd + ps;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (x1 == null || x2 == null) {
|
||||
mx = null;
|
||||
my = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(steps){
|
||||
if (mx !== null && my !== null) {
|
||||
// if middle point exists, transfer p2 -> p1 and p1 -> mp
|
||||
x2 = x1;
|
||||
y2 = y1;
|
||||
x1 = mx;
|
||||
y1 = my;
|
||||
|
||||
// 'remove' middle point
|
||||
mx = null;
|
||||
my = null;
|
||||
|
||||
// subtract pointsize from i to have current point p1 handled again
|
||||
i -= ps;
|
||||
} else if (y1 !== y2 && x1 !== x2) {
|
||||
// create a middle point
|
||||
y2 = y1;
|
||||
mx = x2;
|
||||
my = y1;
|
||||
}
|
||||
}
|
||||
|
||||
// clip x values
|
||||
|
||||
// clip with xmin
|
||||
if (x1 <= x2 && x1 < axisx.min) {
|
||||
if (x2 < axisx.min) {
|
||||
continue;
|
||||
}
|
||||
|
||||
y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
|
||||
x1 = axisx.min;
|
||||
} else if (x2 <= x1 && x2 < axisx.min) {
|
||||
if (x1 < axisx.min) {
|
||||
continue;
|
||||
}
|
||||
|
||||
y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
|
||||
x2 = axisx.min;
|
||||
}
|
||||
|
||||
// clip with xmax
|
||||
if (x1 >= x2 && x1 > axisx.max) {
|
||||
if (x2 > axisx.max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
|
||||
x1 = axisx.max;
|
||||
} else if (x2 >= x1 && x2 > axisx.max) {
|
||||
if (x1 > axisx.max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
|
||||
x2 = axisx.max;
|
||||
}
|
||||
|
||||
if (!areaOpen) {
|
||||
// open area
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
|
||||
areaOpen = true;
|
||||
}
|
||||
|
||||
// now first check the case where both is outside
|
||||
if (y1 >= axisy.max && y2 >= axisy.max) {
|
||||
ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
|
||||
ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
|
||||
continue;
|
||||
} else if (y1 <= axisy.min && y2 <= axisy.min) {
|
||||
ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
|
||||
ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
|
||||
continue;
|
||||
}
|
||||
|
||||
// else it's a bit more complicated, there might
|
||||
// be a flat maxed out rectangle first, then a
|
||||
// triangular cutout or reverse; to find these
|
||||
// keep track of the current x values
|
||||
var x1old = x1,
|
||||
x2old = x2;
|
||||
|
||||
// clip the y values, without shortcutting, we
|
||||
// go through all cases in turn
|
||||
|
||||
// clip with ymin
|
||||
if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
|
||||
x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
|
||||
y1 = axisy.min;
|
||||
} else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
|
||||
x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
|
||||
y2 = axisy.min;
|
||||
}
|
||||
|
||||
// clip with ymax
|
||||
if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
|
||||
x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
|
||||
y1 = axisy.max;
|
||||
} else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
|
||||
x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
|
||||
y2 = axisy.max;
|
||||
}
|
||||
|
||||
// if the x value was changed we got a rectangle
|
||||
// to fill
|
||||
if (x1 !== x1old) {
|
||||
ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
|
||||
// it goes to (x1, y1), but we fill that below
|
||||
}
|
||||
|
||||
// fill triangular section, this sometimes result
|
||||
// in redundant points if (x1, y1) hasn't changed
|
||||
// from previous line to, but we just ignore that
|
||||
ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
|
||||
ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
|
||||
|
||||
// fill the other rectangle if it's there
|
||||
if (x2 !== x2old) {
|
||||
ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
|
||||
ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
|
||||
|
||||
This function is used for drawing lines or area fill. In case the series has line decimation function
|
||||
attached, before starting to draw, as an optimization the points will first be decimated.
|
||||
|
||||
The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
|
||||
plotHeight are the corresponding parameters of flot used to determine the drawing surface.
|
||||
The function getColorOrGradient is used to compute the fill style of lines and area.
|
||||
*/
|
||||
function drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
|
||||
ctx.save();
|
||||
ctx.translate(plotOffset.left, plotOffset.top);
|
||||
ctx.lineJoin = "round";
|
||||
|
||||
if (series.lines.dashes && ctx.setLineDash) {
|
||||
ctx.setLineDash(series.lines.dashes);
|
||||
}
|
||||
|
||||
var datapoints = {
|
||||
format: series.datapoints.format,
|
||||
points: series.datapoints.points,
|
||||
pointsize: series.datapoints.pointsize
|
||||
};
|
||||
|
||||
if (series.decimate) {
|
||||
datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);
|
||||
}
|
||||
|
||||
var lw = series.lines.lineWidth;
|
||||
|
||||
ctx.lineWidth = lw;
|
||||
ctx.strokeStyle = series.color;
|
||||
var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight, getColorOrGradient);
|
||||
if (fillStyle) {
|
||||
ctx.fillStyle = fillStyle;
|
||||
plotLineArea(datapoints, series.xaxis, series.yaxis, series.lines.fillTowards || 0, ctx, series.lines.steps);
|
||||
}
|
||||
|
||||
if (lw > 0) {
|
||||
plotLine(datapoints, 0, 0, series.xaxis, series.yaxis, ctx, series.lines.steps);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
- drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
|
||||
|
||||
This function is used for drawing points using a given symbol. In case the series has points decimation
|
||||
function attached, before starting to draw, as an optimization the points will first be decimated.
|
||||
|
||||
The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
|
||||
plotHeight are the corresponding parameters of flot used to determine the drawing surface.
|
||||
The function drawSymbol is used to compute and draw the symbol chosen for the points.
|
||||
*/
|
||||
function drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
|
||||
function drawCircle(ctx, x, y, radius, shadow, fill) {
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
|
||||
}
|
||||
drawCircle.fill = true;
|
||||
function plotPoints(datapoints, radius, fill, offset, shadow, axisx, axisy, drawSymbolFn) {
|
||||
var points = datapoints.points,
|
||||
ps = datapoints.pointsize;
|
||||
|
||||
ctx.beginPath();
|
||||
for (var i = 0; i < points.length; i += ps) {
|
||||
var x = points[i],
|
||||
y = points[i + 1];
|
||||
if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
x = axisx.p2c(x);
|
||||
y = axisy.p2c(y) + offset;
|
||||
|
||||
drawSymbolFn(ctx, x, y, radius, shadow, fill);
|
||||
}
|
||||
if (drawSymbolFn.fill && !shadow) {
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(plotOffset.left, plotOffset.top);
|
||||
|
||||
var datapoints = {
|
||||
format: series.datapoints.format,
|
||||
points: series.datapoints.points,
|
||||
pointsize: series.datapoints.pointsize
|
||||
};
|
||||
|
||||
if (series.decimatePoints) {
|
||||
datapoints.points = series.decimatePoints(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);
|
||||
}
|
||||
|
||||
var lw = series.points.lineWidth,
|
||||
radius = series.points.radius,
|
||||
symbol = series.points.symbol,
|
||||
drawSymbolFn;
|
||||
|
||||
if (symbol === 'circle') {
|
||||
drawSymbolFn = drawCircle;
|
||||
} else if (typeof symbol === 'string' && drawSymbol && drawSymbol[symbol]) {
|
||||
drawSymbolFn = drawSymbol[symbol];
|
||||
} else if (typeof drawSymbol === 'function') {
|
||||
drawSymbolFn = drawSymbol;
|
||||
}
|
||||
|
||||
// If the user sets the line width to 0, we change it to a very
|
||||
// small value. A line width of 0 seems to force the default of 1.
|
||||
|
||||
if (lw === 0) {
|
||||
lw = 0.0001;
|
||||
}
|
||||
|
||||
ctx.lineWidth = lw;
|
||||
ctx.fillStyle = getFillStyle(series.points, series.color, null, null, getColorOrGradient);
|
||||
ctx.strokeStyle = series.color;
|
||||
plotPoints(datapoints, radius,
|
||||
true, 0, false,
|
||||
series.xaxis, series.yaxis, drawSymbolFn);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
|
||||
var left = x + barLeft,
|
||||
right = x + barRight,
|
||||
bottom = b, top = y,
|
||||
drawLeft, drawRight, drawTop, drawBottom = false,
|
||||
tmp;
|
||||
|
||||
drawLeft = drawRight = drawTop = true;
|
||||
|
||||
// in horizontal mode, we start the bar from the left
|
||||
// instead of from the bottom so it appears to be
|
||||
// horizontal rather than vertical
|
||||
if (horizontal) {
|
||||
drawBottom = drawRight = drawTop = true;
|
||||
drawLeft = false;
|
||||
left = b;
|
||||
right = x;
|
||||
top = y + barLeft;
|
||||
bottom = y + barRight;
|
||||
|
||||
// account for negative bars
|
||||
if (right < left) {
|
||||
tmp = right;
|
||||
right = left;
|
||||
left = tmp;
|
||||
drawLeft = true;
|
||||
drawRight = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
drawLeft = drawRight = drawTop = true;
|
||||
drawBottom = false;
|
||||
left = x + barLeft;
|
||||
right = x + barRight;
|
||||
bottom = b;
|
||||
top = y;
|
||||
|
||||
// account for negative bars
|
||||
if (top < bottom) {
|
||||
tmp = top;
|
||||
top = bottom;
|
||||
bottom = tmp;
|
||||
drawBottom = true;
|
||||
drawTop = false;
|
||||
}
|
||||
}
|
||||
|
||||
// clip
|
||||
if (right < axisx.min || left > axisx.max ||
|
||||
top < axisy.min || bottom > axisy.max) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (left < axisx.min) {
|
||||
left = axisx.min;
|
||||
drawLeft = false;
|
||||
}
|
||||
|
||||
if (right > axisx.max) {
|
||||
right = axisx.max;
|
||||
drawRight = false;
|
||||
}
|
||||
|
||||
if (bottom < axisy.min) {
|
||||
bottom = axisy.min;
|
||||
drawBottom = false;
|
||||
}
|
||||
|
||||
if (top > axisy.max) {
|
||||
top = axisy.max;
|
||||
drawTop = false;
|
||||
}
|
||||
|
||||
left = axisx.p2c(left);
|
||||
bottom = axisy.p2c(bottom);
|
||||
right = axisx.p2c(right);
|
||||
top = axisy.p2c(top);
|
||||
|
||||
// fill the bar
|
||||
if (fillStyleCallback) {
|
||||
c.fillStyle = fillStyleCallback(bottom, top);
|
||||
c.fillRect(left, top, right - left, bottom - top)
|
||||
}
|
||||
|
||||
// draw outline
|
||||
if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
|
||||
c.beginPath();
|
||||
|
||||
// FIXME: inline moveTo is buggy with excanvas
|
||||
c.moveTo(left, bottom);
|
||||
if (drawLeft) {
|
||||
c.lineTo(left, top);
|
||||
} else {
|
||||
c.moveTo(left, top);
|
||||
}
|
||||
|
||||
if (drawTop) {
|
||||
c.lineTo(right, top);
|
||||
} else {
|
||||
c.moveTo(right, top);
|
||||
}
|
||||
|
||||
if (drawRight) {
|
||||
c.lineTo(right, bottom);
|
||||
} else {
|
||||
c.moveTo(right, bottom);
|
||||
}
|
||||
|
||||
if (drawBottom) {
|
||||
c.lineTo(left, bottom);
|
||||
} else {
|
||||
c.moveTo(left, bottom);
|
||||
}
|
||||
|
||||
c.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
|
||||
|
||||
This function is used for drawing series represented as bars. In case the series has decimation
|
||||
function attached, before starting to draw, as an optimization the points will first be decimated.
|
||||
|
||||
The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
|
||||
plotHeight are the corresponding parameters of flot used to determine the drawing surface.
|
||||
The function getColorOrGradient is used to compute the fill style of bars.
|
||||
*/
|
||||
function drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
|
||||
function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
|
||||
var points = datapoints.points,
|
||||
ps = datapoints.pointsize,
|
||||
fillTowards = series.bars.fillTowards || 0,
|
||||
defaultBottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min;
|
||||
|
||||
for (var i = 0; i < points.length; i += ps) {
|
||||
if (points[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use third point as bottom if pointsize is 3
|
||||
var bottom = ps === 3 ? points[i + 2] : defaultBottom;
|
||||
drawBar(points[i], points[i + 1], bottom, barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(plotOffset.left, plotOffset.top);
|
||||
|
||||
var datapoints = {
|
||||
format: series.datapoints.format,
|
||||
points: series.datapoints.points,
|
||||
pointsize: series.datapoints.pointsize
|
||||
};
|
||||
|
||||
if (series.decimate) {
|
||||
datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth);
|
||||
}
|
||||
|
||||
ctx.lineWidth = series.bars.lineWidth;
|
||||
ctx.strokeStyle = series.color;
|
||||
|
||||
var barLeft;
|
||||
var barWidth = series.bars.barWidth[0] || series.bars.barWidth;
|
||||
switch (series.bars.align) {
|
||||
case "left":
|
||||
barLeft = 0;
|
||||
break;
|
||||
case "right":
|
||||
barLeft = -barWidth;
|
||||
break;
|
||||
default:
|
||||
barLeft = -barWidth / 2;
|
||||
}
|
||||
|
||||
var fillStyleCallback = series.bars.fill ? function(bottom, top) {
|
||||
return getFillStyle(series.bars, series.color, bottom, top, getColorOrGradient);
|
||||
} : null;
|
||||
|
||||
plotBars(datapoints, barLeft, barLeft + barWidth, fillStyleCallback, series.xaxis, series.yaxis);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function getFillStyle(filloptions, seriesColor, bottom, top, getColorOrGradient) {
|
||||
var fill = filloptions.fill;
|
||||
if (!fill) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (filloptions.fillColor) {
|
||||
return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
|
||||
}
|
||||
|
||||
var c = $.color.parse(seriesColor);
|
||||
c.a = typeof fill === "number" ? fill : 0.4;
|
||||
c.normalize();
|
||||
return c.toString();
|
||||
}
|
||||
|
||||
this.drawSeriesLines = drawSeriesLines;
|
||||
this.drawSeriesPoints = drawSeriesPoints;
|
||||
this.drawSeriesBars = drawSeriesBars;
|
||||
this.drawBar = drawBar;
|
||||
};
|
||||
|
||||
$.plot.drawSeries = new DrawSeries();
|
||||
})(jQuery);
|
||||
@@ -1,375 +0,0 @@
|
||||
/* Flot plugin for plotting error bars.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
Error bars are used to show standard deviation and other statistical
|
||||
properties in a plot.
|
||||
|
||||
* Created by Rui Pereira - rui (dot) pereira (at) gmail (dot) com
|
||||
|
||||
This plugin allows you to plot error-bars over points. Set "errorbars" inside
|
||||
the points series to the axis name over which there will be error values in
|
||||
your data array (*even* if you do not intend to plot them later, by setting
|
||||
"show: null" on xerr/yerr).
|
||||
|
||||
The plugin supports these options:
|
||||
|
||||
series: {
|
||||
points: {
|
||||
errorbars: "x" or "y" or "xy",
|
||||
xerr: {
|
||||
show: null/false or true,
|
||||
asymmetric: null/false or true,
|
||||
upperCap: null or "-" or function,
|
||||
lowerCap: null or "-" or function,
|
||||
color: null or color,
|
||||
radius: null or number
|
||||
},
|
||||
yerr: { same options as xerr }
|
||||
}
|
||||
}
|
||||
|
||||
Each data point array is expected to be of the type:
|
||||
|
||||
"x" [ x, y, xerr ]
|
||||
"y" [ x, y, yerr ]
|
||||
"xy" [ x, y, xerr, yerr ]
|
||||
|
||||
Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and
|
||||
equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric
|
||||
error-bars on X and asymmetric on Y would be:
|
||||
|
||||
[ x, y, xerr, yerr_lower, yerr_upper ]
|
||||
|
||||
By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will
|
||||
draw a small cap perpendicular to the error bar. They can also be set to a
|
||||
user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg.
|
||||
|
||||
function drawSemiCircle( ctx, x, y, radius ) {
|
||||
ctx.beginPath();
|
||||
ctx.arc( x, y, radius, 0, Math.PI, false );
|
||||
ctx.moveTo( x - radius, y );
|
||||
ctx.lineTo( x + radius, y );
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
Color and radius both default to the same ones of the points series if not
|
||||
set. The independent radius parameter on xerr/yerr is useful for the case when
|
||||
we may want to add error-bars to a line, without showing the interconnecting
|
||||
points (with radius: 0), and still showing end caps on the error-bars.
|
||||
shadowSize and lineWidth are derived as well from the points series.
|
||||
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var options = {
|
||||
series: {
|
||||
points: {
|
||||
errorbars: null, //should be 'x', 'y' or 'xy'
|
||||
xerr: {err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null},
|
||||
yerr: {err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function processRawData(plot, series, data, datapoints) {
|
||||
if (!series.points.errorbars) {
|
||||
return;
|
||||
}
|
||||
|
||||
// x,y values
|
||||
var format = [
|
||||
{ x: true, number: true, required: true },
|
||||
{ y: true, number: true, required: true }
|
||||
];
|
||||
|
||||
var errors = series.points.errorbars;
|
||||
// error bars - first X then Y
|
||||
if (errors === 'x' || errors === 'xy') {
|
||||
// lower / upper error
|
||||
if (series.points.xerr.asymmetric) {
|
||||
format.push({ x: true, number: true, required: true });
|
||||
format.push({ x: true, number: true, required: true });
|
||||
} else {
|
||||
format.push({ x: true, number: true, required: true });
|
||||
}
|
||||
}
|
||||
if (errors === 'y' || errors === 'xy') {
|
||||
// lower / upper error
|
||||
if (series.points.yerr.asymmetric) {
|
||||
format.push({ y: true, number: true, required: true });
|
||||
format.push({ y: true, number: true, required: true });
|
||||
} else {
|
||||
format.push({ y: true, number: true, required: true });
|
||||
}
|
||||
}
|
||||
datapoints.format = format;
|
||||
}
|
||||
|
||||
function parseErrors(series, i) {
|
||||
var points = series.datapoints.points;
|
||||
|
||||
// read errors from points array
|
||||
var exl = null,
|
||||
exu = null,
|
||||
eyl = null,
|
||||
eyu = null;
|
||||
var xerr = series.points.xerr,
|
||||
yerr = series.points.yerr;
|
||||
|
||||
var eb = series.points.errorbars;
|
||||
// error bars - first X
|
||||
if (eb === 'x' || eb === 'xy') {
|
||||
if (xerr.asymmetric) {
|
||||
exl = points[i + 2];
|
||||
exu = points[i + 3];
|
||||
if (eb === 'xy') {
|
||||
if (yerr.asymmetric) {
|
||||
eyl = points[i + 4];
|
||||
eyu = points[i + 5];
|
||||
} else {
|
||||
eyl = points[i + 4];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
exl = points[i + 2];
|
||||
if (eb === 'xy') {
|
||||
if (yerr.asymmetric) {
|
||||
eyl = points[i + 3];
|
||||
eyu = points[i + 4];
|
||||
} else {
|
||||
eyl = points[i + 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
// only Y
|
||||
} else {
|
||||
if (eb === 'y') {
|
||||
if (yerr.asymmetric) {
|
||||
eyl = points[i + 2];
|
||||
eyu = points[i + 3];
|
||||
} else {
|
||||
eyl = points[i + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// symmetric errors?
|
||||
if (exu == null) exu = exl;
|
||||
if (eyu == null) eyu = eyl;
|
||||
|
||||
var errRanges = [exl, exu, eyl, eyu];
|
||||
// nullify if not showing
|
||||
if (!xerr.show) {
|
||||
errRanges[0] = null;
|
||||
errRanges[1] = null;
|
||||
}
|
||||
if (!yerr.show) {
|
||||
errRanges[2] = null;
|
||||
errRanges[3] = null;
|
||||
}
|
||||
return errRanges;
|
||||
}
|
||||
|
||||
function drawSeriesErrors(plot, ctx, s) {
|
||||
var points = s.datapoints.points,
|
||||
ps = s.datapoints.pointsize,
|
||||
ax = [s.xaxis, s.yaxis],
|
||||
radius = s.points.radius,
|
||||
err = [s.points.xerr, s.points.yerr],
|
||||
tmp;
|
||||
|
||||
//sanity check, in case some inverted axis hack is applied to flot
|
||||
var invertX = false;
|
||||
if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) {
|
||||
invertX = true;
|
||||
tmp = err[0].lowerCap;
|
||||
err[0].lowerCap = err[0].upperCap;
|
||||
err[0].upperCap = tmp;
|
||||
}
|
||||
|
||||
var invertY = false;
|
||||
if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) {
|
||||
invertY = true;
|
||||
tmp = err[1].lowerCap;
|
||||
err[1].lowerCap = err[1].upperCap;
|
||||
err[1].upperCap = tmp;
|
||||
}
|
||||
|
||||
for (var i = 0; i < s.datapoints.points.length; i += ps) {
|
||||
//parse
|
||||
var errRanges = parseErrors(s, i);
|
||||
|
||||
//cycle xerr & yerr
|
||||
for (var e = 0; e < err.length; e++) {
|
||||
var minmax = [ax[e].min, ax[e].max];
|
||||
|
||||
//draw this error?
|
||||
if (errRanges[e * err.length]) {
|
||||
//data coordinates
|
||||
var x = points[i],
|
||||
y = points[i + 1];
|
||||
|
||||
//errorbar ranges
|
||||
var upper = [x, y][e] + errRanges[e * err.length + 1],
|
||||
lower = [x, y][e] - errRanges[e * err.length];
|
||||
|
||||
//points outside of the canvas
|
||||
if (err[e].err === 'x') {
|
||||
if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (err[e].err === 'y') {
|
||||
if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// prevent errorbars getting out of the canvas
|
||||
var drawUpper = true,
|
||||
drawLower = true;
|
||||
|
||||
if (upper > minmax[1]) {
|
||||
drawUpper = false;
|
||||
upper = minmax[1];
|
||||
}
|
||||
if (lower < minmax[0]) {
|
||||
drawLower = false;
|
||||
lower = minmax[0];
|
||||
}
|
||||
|
||||
//sanity check, in case some inverted axis hack is applied to flot
|
||||
if ((err[e].err === 'x' && invertX) || (err[e].err === 'y' && invertY)) {
|
||||
//swap coordinates
|
||||
tmp = lower;
|
||||
lower = upper;
|
||||
upper = tmp;
|
||||
tmp = drawLower;
|
||||
drawLower = drawUpper;
|
||||
drawUpper = tmp;
|
||||
tmp = minmax[0];
|
||||
minmax[0] = minmax[1];
|
||||
minmax[1] = tmp;
|
||||
}
|
||||
|
||||
// convert to pixels
|
||||
x = ax[0].p2c(x);
|
||||
y = ax[1].p2c(y);
|
||||
upper = ax[e].p2c(upper);
|
||||
lower = ax[e].p2c(lower);
|
||||
minmax[0] = ax[e].p2c(minmax[0]);
|
||||
minmax[1] = ax[e].p2c(minmax[1]);
|
||||
|
||||
//same style as points by default
|
||||
var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth,
|
||||
sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize;
|
||||
|
||||
//shadow as for points
|
||||
if (lw > 0 && sw > 0) {
|
||||
var w = sw / 2;
|
||||
ctx.lineWidth = w;
|
||||
ctx.strokeStyle = "rgba(0,0,0,0.1)";
|
||||
drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w / 2, minmax);
|
||||
|
||||
ctx.strokeStyle = "rgba(0,0,0,0.2)";
|
||||
drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w / 2, minmax);
|
||||
}
|
||||
|
||||
ctx.strokeStyle = err[e].color
|
||||
? err[e].color
|
||||
: s.color;
|
||||
ctx.lineWidth = lw;
|
||||
//draw it
|
||||
drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawError(ctx, err, x, y, upper, lower, drawUpper, drawLower, radius, offset, minmax) {
|
||||
//shadow offset
|
||||
y += offset;
|
||||
upper += offset;
|
||||
lower += offset;
|
||||
|
||||
// error bar - avoid plotting over circles
|
||||
if (err.err === 'x') {
|
||||
if (upper > x + radius) drawPath(ctx, [[upper, y], [Math.max(x + radius, minmax[0]), y]]);
|
||||
else drawUpper = false;
|
||||
|
||||
if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius, minmax[1]), y], [lower, y]]);
|
||||
else drawLower = false;
|
||||
} else {
|
||||
if (upper < y - radius) drawPath(ctx, [[x, upper], [x, Math.min(y - radius, minmax[0])]]);
|
||||
else drawUpper = false;
|
||||
|
||||
if (lower > y + radius) drawPath(ctx, [[x, Math.max(y + radius, minmax[1])], [x, lower]]);
|
||||
else drawLower = false;
|
||||
}
|
||||
|
||||
//internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps
|
||||
//this is a way to get errorbars on lines without visible connecting dots
|
||||
radius = err.radius != null
|
||||
? err.radius
|
||||
: radius;
|
||||
|
||||
// upper cap
|
||||
if (drawUpper) {
|
||||
if (err.upperCap === '-') {
|
||||
if (err.err === 'x') drawPath(ctx, [[upper, y - radius], [upper, y + radius]]);
|
||||
else drawPath(ctx, [[x - radius, upper], [x + radius, upper]]);
|
||||
} else if ($.isFunction(err.upperCap)) {
|
||||
if (err.err === 'x') err.upperCap(ctx, upper, y, radius);
|
||||
else err.upperCap(ctx, x, upper, radius);
|
||||
}
|
||||
}
|
||||
// lower cap
|
||||
if (drawLower) {
|
||||
if (err.lowerCap === '-') {
|
||||
if (err.err === 'x') drawPath(ctx, [[lower, y - radius], [lower, y + radius]]);
|
||||
else drawPath(ctx, [[x - radius, lower], [x + radius, lower]]);
|
||||
} else if ($.isFunction(err.lowerCap)) {
|
||||
if (err.err === 'x') err.lowerCap(ctx, lower, y, radius);
|
||||
else err.lowerCap(ctx, x, lower, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawPath(ctx, pts) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pts[0][0], pts[0][1]);
|
||||
for (var p = 1; p < pts.length; p++) {
|
||||
ctx.lineTo(pts[p][0], pts[p][1]);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function draw(plot, ctx) {
|
||||
var plotOffset = plot.getPlotOffset();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(plotOffset.left, plotOffset.top);
|
||||
$.each(plot.getData(), function (i, s) {
|
||||
if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show)) {
|
||||
drawSeriesErrors(plot, ctx, s);
|
||||
}
|
||||
});
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function init(plot) {
|
||||
plot.hooks.processRawData.push(processRawData);
|
||||
plot.hooks.draw.push(draw);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'errorbars',
|
||||
version: '1.0'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,254 +0,0 @@
|
||||
/* Flot plugin for computing bottoms for filled line and bar charts.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The case: you've got two series that you want to fill the area between. In Flot
|
||||
terms, you need to use one as the fill bottom of the other. You can specify the
|
||||
bottom of each data point as the third coordinate manually, or you can use this
|
||||
plugin to compute it for you.
|
||||
|
||||
In order to name the other series, you need to give it an id, like this:
|
||||
|
||||
var dataset = [
|
||||
{ data: [ ... ], id: "foo" } , // use default bottom
|
||||
{ data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
|
||||
];
|
||||
|
||||
$.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
|
||||
|
||||
As a convenience, if the id given is a number that doesn't appear as an id in
|
||||
the series, it is interpreted as the index in the array instead (so fillBetween:
|
||||
0 can also mean the first series).
|
||||
|
||||
Internally, the plugin modifies the datapoints in each series. For line series,
|
||||
extra data points might be inserted through interpolation. Note that at points
|
||||
where the bottom line is not defined (due to a null point or start/end of line),
|
||||
the current line will show a gap too. The algorithm comes from the
|
||||
jquery.flot.stack.js plugin, possibly some code could be shared.
|
||||
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var options = {
|
||||
series: {
|
||||
fillBetween: null // or number
|
||||
}
|
||||
};
|
||||
|
||||
function init(plot) {
|
||||
function findBottomSeries(s, allseries) {
|
||||
var i;
|
||||
|
||||
for (i = 0; i < allseries.length; ++i) {
|
||||
if (allseries[ i ].id === s.fillBetween) {
|
||||
return allseries[ i ];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof s.fillBetween === "number") {
|
||||
if (s.fillBetween < 0 || s.fillBetween >= allseries.length) {
|
||||
return null;
|
||||
}
|
||||
return allseries[ s.fillBetween ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function computeFormat(plot, s, data, datapoints) {
|
||||
if (s.fillBetween == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
format = datapoints.format;
|
||||
var plotHasId = function(id) {
|
||||
var plotData = plot.getData();
|
||||
for (i = 0; i < plotData.length; i++) {
|
||||
if (plotData[i].id === id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!format) {
|
||||
format = [];
|
||||
|
||||
format.push({
|
||||
x: true,
|
||||
number: true,
|
||||
computeRange: s.xaxis.options.autoScale !== 'none',
|
||||
required: true
|
||||
});
|
||||
format.push({
|
||||
y: true,
|
||||
number: true,
|
||||
computeRange: s.yaxis.options.autoScale !== 'none',
|
||||
required: true
|
||||
});
|
||||
|
||||
if (s.fillBetween !== undefined && s.fillBetween !== '' && plotHasId(s.fillBetween) && s.fillBetween !== s.id) {
|
||||
format.push({
|
||||
x: false,
|
||||
y: true,
|
||||
number: true,
|
||||
required: false,
|
||||
computeRange: s.yaxis.options.autoScale !== 'none',
|
||||
defaultValue: 0
|
||||
});
|
||||
}
|
||||
|
||||
datapoints.format = format;
|
||||
}
|
||||
}
|
||||
|
||||
function computeFillBottoms(plot, s, datapoints) {
|
||||
if (s.fillBetween == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var other = findBottomSeries(s, plot.getData());
|
||||
|
||||
if (!other) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ps = datapoints.pointsize,
|
||||
points = datapoints.points,
|
||||
otherps = other.datapoints.pointsize,
|
||||
otherpoints = other.datapoints.points,
|
||||
newpoints = [],
|
||||
px, py, intery, qx, qy, bottom,
|
||||
withlines = s.lines.show,
|
||||
withbottom = ps > 2 && datapoints.format[2].y,
|
||||
withsteps = withlines && s.lines.steps,
|
||||
fromgap = true,
|
||||
i = 0,
|
||||
j = 0,
|
||||
l, m;
|
||||
|
||||
while (true) {
|
||||
if (i >= points.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
l = newpoints.length;
|
||||
|
||||
if (points[ i ] == null) {
|
||||
// copy gaps
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints.push(points[ i + m ]);
|
||||
}
|
||||
|
||||
i += ps;
|
||||
} else if (j >= otherpoints.length) {
|
||||
// for lines, we can't use the rest of the points
|
||||
if (!withlines) {
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints.push(points[ i + m ]);
|
||||
}
|
||||
}
|
||||
|
||||
i += ps;
|
||||
} else if (otherpoints[ j ] == null) {
|
||||
// oops, got a gap
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints.push(null);
|
||||
}
|
||||
|
||||
fromgap = true;
|
||||
j += otherps;
|
||||
} else {
|
||||
// cases where we actually got two points
|
||||
px = points[ i ];
|
||||
py = points[ i + 1 ];
|
||||
qx = otherpoints[ j ];
|
||||
qy = otherpoints[ j + 1 ];
|
||||
bottom = 0;
|
||||
|
||||
if (px === qx) {
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints.push(points[ i + m ]);
|
||||
}
|
||||
|
||||
//newpoints[ l + 1 ] += qy;
|
||||
bottom = qy;
|
||||
|
||||
i += ps;
|
||||
j += otherps;
|
||||
} else if (px > qx) {
|
||||
// we got past point below, might need to
|
||||
// insert interpolated extra point
|
||||
|
||||
if (withlines && i > 0 && points[ i - ps ] != null) {
|
||||
intery = py + (points[ i - ps + 1 ] - py) * (qx - px) / (points[ i - ps ] - px);
|
||||
newpoints.push(qx);
|
||||
newpoints.push(intery);
|
||||
for (m = 2; m < ps; ++m) {
|
||||
newpoints.push(points[ i + m ]);
|
||||
}
|
||||
bottom = qy;
|
||||
}
|
||||
|
||||
j += otherps;
|
||||
} else {
|
||||
// px < qx
|
||||
// if we come from a gap, we just skip this point
|
||||
|
||||
if (fromgap && withlines) {
|
||||
i += ps;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints.push(points[ i + m ]);
|
||||
}
|
||||
|
||||
// we might be able to interpolate a point below,
|
||||
// this can give us a better y
|
||||
|
||||
if (withlines && j > 0 && otherpoints[ j - otherps ] != null) {
|
||||
bottom = qy + (otherpoints[ j - otherps + 1 ] - qy) * (px - qx) / (otherpoints[ j - otherps ] - qx);
|
||||
}
|
||||
|
||||
//newpoints[l + 1] += bottom;
|
||||
|
||||
i += ps;
|
||||
}
|
||||
|
||||
fromgap = false;
|
||||
|
||||
if (l !== newpoints.length && withbottom) {
|
||||
newpoints[ l + 2 ] = bottom;
|
||||
}
|
||||
}
|
||||
|
||||
// maintain the line steps invariant
|
||||
|
||||
if (withsteps && l !== newpoints.length && l > 0 &&
|
||||
newpoints[ l ] !== null &&
|
||||
newpoints[ l ] !== newpoints[ l - ps ] &&
|
||||
newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ]) {
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints[ l + ps + m ] = newpoints[ l + m ];
|
||||
}
|
||||
newpoints[ l + 1 ] = newpoints[ l - ps + 1 ];
|
||||
}
|
||||
}
|
||||
|
||||
datapoints.points = newpoints;
|
||||
}
|
||||
|
||||
plot.hooks.processRawData.push(computeFormat);
|
||||
plot.hooks.processDatapoints.push(computeFillBottoms);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: "fillbetween",
|
||||
version: "1.0"
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,47 +0,0 @@
|
||||
/* Support for flat 1D data series.
|
||||
|
||||
A 1D flat data series is a data series in the form of a regular 1D array. The
|
||||
main reason for using a flat data series is that it performs better, consumes
|
||||
less memory and generates less garbage collection than the regular flot format.
|
||||
|
||||
Example:
|
||||
|
||||
plot.setData([[[0,0], [1,1], [2,2], [3,3]]]); // regular flot format
|
||||
plot.setData([{flatdata: true, data: [0, 1, 2, 3]}]); // flatdata format
|
||||
|
||||
Set series.flatdata to true to enable this plugin.
|
||||
|
||||
You can use series.start to specify the starting index of the series (default is 0)
|
||||
You can use series.step to specify the interval between consecutive indexes of the series (default is 1)
|
||||
*/
|
||||
|
||||
/* global jQuery*/
|
||||
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
function process1DRawData(plot, series, data, datapoints) {
|
||||
if (series.flatdata === true) {
|
||||
var start = series.start || 0;
|
||||
var step = typeof series.step === 'number' ? series.step : 1;
|
||||
datapoints.pointsize = 2;
|
||||
for (var i = 0, j = 0; i < data.length; i++, j += 2) {
|
||||
datapoints.points[j] = start + (i * step);
|
||||
datapoints.points[j + 1] = data[i];
|
||||
}
|
||||
if (datapoints.points !== undefined) {
|
||||
datapoints.points.length = data.length * 2;
|
||||
} else {
|
||||
datapoints.points = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: function(plot) {
|
||||
plot.hooks.processRawData.push(process1DRawData);
|
||||
},
|
||||
name: 'flatdata',
|
||||
version: '0.0.2'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,350 +0,0 @@
|
||||
/* global jQuery */
|
||||
|
||||
/**
|
||||
## jquery.flot.hover.js
|
||||
|
||||
This plugin is used for mouse hover and tap on a point of plot series.
|
||||
It supports the following options:
|
||||
```js
|
||||
grid: {
|
||||
hoverable: false, //to trigger plothover event on mouse hover or tap on a point
|
||||
clickable: false //to trigger plotclick event on mouse hover
|
||||
}
|
||||
```
|
||||
|
||||
It listens to native mouse move event or click, as well as artificial generated
|
||||
tap and touchevent.
|
||||
|
||||
When the mouse is over a point or a tap on a point is performed, that point or
|
||||
the correscponding bar will be highlighted and a "plothover" event will be generated.
|
||||
|
||||
Custom "touchevent" is triggered when any touch interaction is made. Hover plugin
|
||||
handles this events by unhighlighting all of the previously highlighted points and generates
|
||||
"plothovercleanup" event to notify any part that is handling plothover (for exemple to cleanup
|
||||
the tooltip from webcharts).
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var options = {
|
||||
grid: {
|
||||
hoverable: false,
|
||||
clickable: false
|
||||
}
|
||||
};
|
||||
|
||||
var browser = $.plot.browser;
|
||||
|
||||
var eventType = {
|
||||
click: 'click',
|
||||
hover: 'hover'
|
||||
}
|
||||
|
||||
function init(plot) {
|
||||
var lastMouseMoveEvent;
|
||||
var highlights = [];
|
||||
|
||||
function bindEvents(plot, eventHolder) {
|
||||
var o = plot.getOptions();
|
||||
|
||||
if (o.grid.hoverable || o.grid.clickable) {
|
||||
eventHolder[0].addEventListener('touchevent', triggerCleanupEvent, false);
|
||||
eventHolder[0].addEventListener('tap', generatePlothoverEvent, false);
|
||||
}
|
||||
|
||||
if (o.grid.clickable) {
|
||||
eventHolder.bind("click", onClick);
|
||||
}
|
||||
|
||||
if (o.grid.hoverable) {
|
||||
eventHolder.bind("mousemove", onMouseMove);
|
||||
|
||||
// Use bind, rather than .mouseleave, because we officially
|
||||
// still support jQuery 1.2.6, which doesn't define a shortcut
|
||||
// for mouseenter or mouseleave. This was a bug/oversight that
|
||||
// was fixed somewhere around 1.3.x. We can return to using
|
||||
// .mouseleave when we drop support for 1.2.6.
|
||||
|
||||
eventHolder.bind("mouseleave", onMouseLeave);
|
||||
}
|
||||
}
|
||||
|
||||
function shutdown(plot, eventHolder) {
|
||||
eventHolder[0].removeEventListener('tap', generatePlothoverEvent);
|
||||
eventHolder[0].removeEventListener('touchevent', triggerCleanupEvent);
|
||||
eventHolder.unbind("mousemove", onMouseMove);
|
||||
eventHolder.unbind("mouseleave", onMouseLeave);
|
||||
eventHolder.unbind("click", onClick);
|
||||
highlights = [];
|
||||
}
|
||||
|
||||
|
||||
function generatePlothoverEvent(e) {
|
||||
var o = plot.getOptions(),
|
||||
newEvent = new CustomEvent('mouseevent');
|
||||
|
||||
//transform from touch event to mouse event format
|
||||
newEvent.pageX = e.detail.changedTouches[0].pageX;
|
||||
newEvent.pageY = e.detail.changedTouches[0].pageY;
|
||||
newEvent.clientX = e.detail.changedTouches[0].clientX;
|
||||
newEvent.clientY = e.detail.changedTouches[0].clientY;
|
||||
|
||||
if (o.grid.hoverable) {
|
||||
doTriggerClickHoverEvent(newEvent, eventType.hover, 30);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function doTriggerClickHoverEvent(event, eventType, searchDistance) {
|
||||
var series = plot.getData();
|
||||
if (event !== undefined
|
||||
&& series.length > 0
|
||||
&& series[0].xaxis.c2p !== undefined
|
||||
&& series[0].yaxis.c2p !== undefined) {
|
||||
var eventToTrigger = "plot" + eventType;
|
||||
var seriesFlag = eventType + "able";
|
||||
triggerClickHoverEvent(eventToTrigger, event,
|
||||
function(i) {
|
||||
return series[i][seriesFlag] !== false;
|
||||
}, searchDistance);
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseMove(e) {
|
||||
lastMouseMoveEvent = e;
|
||||
plot.getPlaceholder()[0].lastMouseMoveEvent = e;
|
||||
doTriggerClickHoverEvent(e, eventType.hover);
|
||||
}
|
||||
|
||||
function onMouseLeave(e) {
|
||||
lastMouseMoveEvent = undefined;
|
||||
plot.getPlaceholder()[0].lastMouseMoveEvent = undefined;
|
||||
triggerClickHoverEvent("plothover", e,
|
||||
function(i) {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function onClick(e) {
|
||||
doTriggerClickHoverEvent(e, eventType.click);
|
||||
}
|
||||
|
||||
function triggerCleanupEvent() {
|
||||
plot.unhighlight();
|
||||
plot.getPlaceholder().trigger('plothovercleanup');
|
||||
}
|
||||
|
||||
// trigger click or hover event (they send the same parameters
|
||||
// so we share their code)
|
||||
function triggerClickHoverEvent(eventname, event, seriesFilter, searchDistance) {
|
||||
var options = plot.getOptions(),
|
||||
offset = plot.offset(),
|
||||
page = browser.getPageXY(event),
|
||||
canvasX = page.X - offset.left,
|
||||
canvasY = page.Y - offset.top,
|
||||
pos = plot.c2p({
|
||||
left: canvasX,
|
||||
top: canvasY
|
||||
}),
|
||||
distance = searchDistance !== undefined ? searchDistance : options.grid.mouseActiveRadius;
|
||||
|
||||
pos.pageX = page.X;
|
||||
pos.pageY = page.Y;
|
||||
|
||||
var item = plot.findNearbyItem(canvasX, canvasY, seriesFilter, distance);
|
||||
|
||||
if (item) {
|
||||
// fill in mouse pos for any listeners out there
|
||||
item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left, 10);
|
||||
item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top, 10);
|
||||
}
|
||||
|
||||
if (options.grid.autoHighlight) {
|
||||
// clear auto-highlights
|
||||
for (var i = 0; i < highlights.length; ++i) {
|
||||
var h = highlights[i];
|
||||
if ((h.auto === eventname &&
|
||||
!(item && h.series === item.series &&
|
||||
h.point[0] === item.datapoint[0] &&
|
||||
h.point[1] === item.datapoint[1])) || !item) {
|
||||
unhighlight(h.series, h.point);
|
||||
}
|
||||
}
|
||||
|
||||
if (item) {
|
||||
highlight(item.series, item.datapoint, eventname);
|
||||
}
|
||||
}
|
||||
|
||||
plot.getPlaceholder().trigger(eventname, [pos, item]);
|
||||
}
|
||||
|
||||
function highlight(s, point, auto) {
|
||||
if (typeof s === "number") {
|
||||
s = plot.getData()[s];
|
||||
}
|
||||
|
||||
if (typeof point === "number") {
|
||||
var ps = s.datapoints.pointsize;
|
||||
point = s.datapoints.points.slice(ps * point, ps * (point + 1));
|
||||
}
|
||||
|
||||
var i = indexOfHighlight(s, point);
|
||||
if (i === -1) {
|
||||
highlights.push({
|
||||
series: s,
|
||||
point: point,
|
||||
auto: auto
|
||||
});
|
||||
|
||||
plot.triggerRedrawOverlay();
|
||||
} else if (!auto) {
|
||||
highlights[i].auto = false;
|
||||
}
|
||||
}
|
||||
|
||||
function unhighlight(s, point) {
|
||||
if (s == null && point == null) {
|
||||
highlights = [];
|
||||
plot.triggerRedrawOverlay();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof s === "number") {
|
||||
s = plot.getData()[s];
|
||||
}
|
||||
|
||||
if (typeof point === "number") {
|
||||
var ps = s.datapoints.pointsize;
|
||||
point = s.datapoints.points.slice(ps * point, ps * (point + 1));
|
||||
}
|
||||
|
||||
var i = indexOfHighlight(s, point);
|
||||
if (i !== -1) {
|
||||
highlights.splice(i, 1);
|
||||
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
function indexOfHighlight(s, p) {
|
||||
for (var i = 0; i < highlights.length; ++i) {
|
||||
var h = highlights[i];
|
||||
if (h.series === s &&
|
||||
h.point[0] === p[0] &&
|
||||
h.point[1] === p[1]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
function processDatapoints() {
|
||||
triggerCleanupEvent();
|
||||
doTriggerClickHoverEvent(lastMouseMoveEvent, eventType.hover);
|
||||
}
|
||||
|
||||
function setupGrid() {
|
||||
doTriggerClickHoverEvent(lastMouseMoveEvent, eventType.hover);
|
||||
}
|
||||
|
||||
function drawOverlay(plot, octx, overlay) {
|
||||
var plotOffset = plot.getPlotOffset(),
|
||||
i, hi;
|
||||
|
||||
octx.save();
|
||||
octx.translate(plotOffset.left, plotOffset.top);
|
||||
for (i = 0; i < highlights.length; ++i) {
|
||||
hi = highlights[i];
|
||||
|
||||
if (hi.series.bars.show) drawBarHighlight(hi.series, hi.point, octx);
|
||||
else drawPointHighlight(hi.series, hi.point, octx, plot);
|
||||
}
|
||||
octx.restore();
|
||||
}
|
||||
|
||||
function drawPointHighlight(series, point, octx, plot) {
|
||||
var x = point[0],
|
||||
y = point[1],
|
||||
axisx = series.xaxis,
|
||||
axisy = series.yaxis,
|
||||
highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
|
||||
|
||||
if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pointRadius = series.points.radius + series.points.lineWidth / 2;
|
||||
octx.lineWidth = pointRadius;
|
||||
octx.strokeStyle = highlightColor;
|
||||
var radius = 1.5 * pointRadius;
|
||||
x = axisx.p2c(x);
|
||||
y = axisy.p2c(y);
|
||||
|
||||
octx.beginPath();
|
||||
var symbol = series.points.symbol;
|
||||
if (symbol === 'circle') {
|
||||
octx.arc(x, y, radius, 0, 2 * Math.PI, false);
|
||||
} else if (typeof symbol === 'string' && plot.drawSymbol && plot.drawSymbol[symbol]) {
|
||||
plot.drawSymbol[symbol](octx, x, y, radius, false);
|
||||
}
|
||||
|
||||
octx.closePath();
|
||||
octx.stroke();
|
||||
}
|
||||
|
||||
function drawBarHighlight(series, point, octx) {
|
||||
var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
|
||||
fillStyle = highlightColor,
|
||||
barLeft;
|
||||
|
||||
var barWidth = series.bars.barWidth[0] || series.bars.barWidth;
|
||||
switch (series.bars.align) {
|
||||
case "left":
|
||||
barLeft = 0;
|
||||
break;
|
||||
case "right":
|
||||
barLeft = -barWidth;
|
||||
break;
|
||||
default:
|
||||
barLeft = -barWidth / 2;
|
||||
}
|
||||
|
||||
octx.lineWidth = series.bars.lineWidth;
|
||||
octx.strokeStyle = highlightColor;
|
||||
|
||||
var fillTowards = series.bars.fillTowards || 0,
|
||||
bottom = fillTowards > series.yaxis.min ? Math.min(series.yaxis.max, fillTowards) : series.yaxis.min;
|
||||
|
||||
$.plot.drawSeries.drawBar(point[0], point[1], point[2] || bottom, barLeft, barLeft + barWidth,
|
||||
function() {
|
||||
return fillStyle;
|
||||
}, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
|
||||
}
|
||||
|
||||
function initHover(plot, options) {
|
||||
plot.highlight = highlight;
|
||||
plot.unhighlight = unhighlight;
|
||||
if (options.grid.hoverable || options.grid.clickable) {
|
||||
plot.hooks.drawOverlay.push(drawOverlay);
|
||||
plot.hooks.processDatapoints.push(processDatapoints);
|
||||
plot.hooks.setupGrid.push(setupGrid);
|
||||
}
|
||||
|
||||
lastMouseMoveEvent = plot.getPlaceholder()[0].lastMouseMoveEvent;
|
||||
}
|
||||
|
||||
plot.hooks.bindEvents.push(bindEvents);
|
||||
plot.hooks.shutdown.push(shutdown);
|
||||
plot.hooks.processOptions.push(initHover);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'hover',
|
||||
version: '0.1'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,249 +0,0 @@
|
||||
/* Flot plugin for plotting images.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and
|
||||
(x2, y2) are where you intend the two opposite corners of the image to end up
|
||||
in the plot. Image must be a fully loaded Javascript image (you can make one
|
||||
with new Image()). If the image is not complete, it's skipped when plotting.
|
||||
|
||||
There are two helpers included for retrieving images. The easiest work the way
|
||||
that you put in URLs instead of images in the data, like this:
|
||||
|
||||
[ "myimage.png", 0, 0, 10, 10 ]
|
||||
|
||||
Then call $.plot.image.loadData( data, options, callback ) where data and
|
||||
options are the same as you pass in to $.plot. This loads the images, replaces
|
||||
the URLs in the data with the corresponding images and calls "callback" when
|
||||
all images are loaded (or failed loading). In the callback, you can then call
|
||||
$.plot with the data set. See the included example.
|
||||
|
||||
A more low-level helper, $.plot.image.load(urls, callback) is also included.
|
||||
Given a list of URLs, it calls callback with an object mapping from URL to
|
||||
Image object when all images are loaded or have failed loading.
|
||||
|
||||
The plugin supports these options:
|
||||
|
||||
series: {
|
||||
images: {
|
||||
show: boolean
|
||||
anchor: "corner" or "center"
|
||||
alpha: [ 0, 1 ]
|
||||
}
|
||||
}
|
||||
|
||||
They can be specified for a specific series:
|
||||
|
||||
$.plot( $("#placeholder"), [{
|
||||
data: [ ... ],
|
||||
images: { ... }
|
||||
])
|
||||
|
||||
Note that because the data format is different from usual data points, you
|
||||
can't use images with anything else in a specific data series.
|
||||
|
||||
Setting "anchor" to "center" causes the pixels in the image to be anchored at
|
||||
the corner pixel centers inside of at the pixel corners, effectively letting
|
||||
half a pixel stick out to each side in the plot.
|
||||
|
||||
A possible future direction could be support for tiling for large images (like
|
||||
Google Maps).
|
||||
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var options = {
|
||||
series: {
|
||||
images: {
|
||||
show: false,
|
||||
alpha: 1,
|
||||
anchor: "corner" // or "center"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.plot.image = {};
|
||||
|
||||
$.plot.image.loadDataImages = function (series, options, callback) {
|
||||
var urls = [], points = [];
|
||||
|
||||
var defaultShow = options.series.images.show;
|
||||
|
||||
$.each(series, function (i, s) {
|
||||
if (!(defaultShow || s.images.show)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s.data) {
|
||||
s = s.data;
|
||||
}
|
||||
|
||||
$.each(s, function (i, p) {
|
||||
if (typeof p[0] === "string") {
|
||||
urls.push(p[0]);
|
||||
points.push(p);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$.plot.image.load(urls, function (loadedImages) {
|
||||
$.each(points, function (i, p) {
|
||||
var url = p[0];
|
||||
if (loadedImages[url]) {
|
||||
p[0] = loadedImages[url];
|
||||
}
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
$.plot.image.load = function (urls, callback) {
|
||||
var missing = urls.length, loaded = {};
|
||||
if (missing === 0) {
|
||||
callback({});
|
||||
}
|
||||
|
||||
$.each(urls, function (i, url) {
|
||||
var handler = function () {
|
||||
--missing;
|
||||
loaded[url] = this;
|
||||
|
||||
if (missing === 0) {
|
||||
callback(loaded);
|
||||
}
|
||||
};
|
||||
|
||||
$('<img />').load(handler).error(handler).attr('src', url);
|
||||
});
|
||||
};
|
||||
|
||||
function drawSeries(plot, ctx, series) {
|
||||
var plotOffset = plot.getPlotOffset();
|
||||
|
||||
if (!series.images || !series.images.show) {
|
||||
return;
|
||||
}
|
||||
|
||||
var points = series.datapoints.points,
|
||||
ps = series.datapoints.pointsize;
|
||||
|
||||
for (var i = 0; i < points.length; i += ps) {
|
||||
var img = points[i],
|
||||
x1 = points[i + 1], y1 = points[i + 2],
|
||||
x2 = points[i + 3], y2 = points[i + 4],
|
||||
xaxis = series.xaxis, yaxis = series.yaxis,
|
||||
tmp;
|
||||
|
||||
// actually we should check img.complete, but it
|
||||
// appears to be a somewhat unreliable indicator in
|
||||
// IE6 (false even after load event)
|
||||
if (!img || img.width <= 0 || img.height <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (x1 > x2) {
|
||||
tmp = x2;
|
||||
x2 = x1;
|
||||
x1 = tmp;
|
||||
}
|
||||
if (y1 > y2) {
|
||||
tmp = y2;
|
||||
y2 = y1;
|
||||
y1 = tmp;
|
||||
}
|
||||
|
||||
// if the anchor is at the center of the pixel, expand the
|
||||
// image by 1/2 pixel in each direction
|
||||
if (series.images.anchor === "center") {
|
||||
tmp = 0.5 * (x2 - x1) / (img.width - 1);
|
||||
x1 -= tmp;
|
||||
x2 += tmp;
|
||||
tmp = 0.5 * (y2 - y1) / (img.height - 1);
|
||||
y1 -= tmp;
|
||||
y2 += tmp;
|
||||
}
|
||||
|
||||
// clip
|
||||
if (x1 === x2 || y1 === y2 ||
|
||||
x1 >= xaxis.max || x2 <= xaxis.min ||
|
||||
y1 >= yaxis.max || y2 <= yaxis.min) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height;
|
||||
if (x1 < xaxis.min) {
|
||||
sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1);
|
||||
x1 = xaxis.min;
|
||||
}
|
||||
|
||||
if (x2 > xaxis.max) {
|
||||
sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1);
|
||||
x2 = xaxis.max;
|
||||
}
|
||||
|
||||
if (y1 < yaxis.min) {
|
||||
sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1);
|
||||
y1 = yaxis.min;
|
||||
}
|
||||
|
||||
if (y2 > yaxis.max) {
|
||||
sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1);
|
||||
y2 = yaxis.max;
|
||||
}
|
||||
|
||||
x1 = xaxis.p2c(x1);
|
||||
x2 = xaxis.p2c(x2);
|
||||
y1 = yaxis.p2c(y1);
|
||||
y2 = yaxis.p2c(y2);
|
||||
|
||||
// the transformation may have swapped us
|
||||
if (x1 > x2) {
|
||||
tmp = x2;
|
||||
x2 = x1;
|
||||
x1 = tmp;
|
||||
}
|
||||
if (y1 > y2) {
|
||||
tmp = y2;
|
||||
y2 = y1;
|
||||
y1 = tmp;
|
||||
}
|
||||
|
||||
tmp = ctx.globalAlpha;
|
||||
ctx.globalAlpha *= series.images.alpha;
|
||||
ctx.drawImage(img,
|
||||
sx1, sy1, sx2 - sx1, sy2 - sy1,
|
||||
x1 + plotOffset.left, y1 + plotOffset.top,
|
||||
x2 - x1, y2 - y1);
|
||||
ctx.globalAlpha = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
function processRawData(plot, series, data, datapoints) {
|
||||
if (!series.images.show) {
|
||||
return;
|
||||
}
|
||||
|
||||
// format is Image, x1, y1, x2, y2 (opposite corners)
|
||||
datapoints.format = [
|
||||
{ required: true },
|
||||
{ x: true, number: true, required: true },
|
||||
{ y: true, number: true, required: true },
|
||||
{ x: true, number: true, required: true },
|
||||
{ y: true, number: true, required: true }
|
||||
];
|
||||
}
|
||||
|
||||
function init(plot) {
|
||||
plot.hooks.processRawData.push(processRawData);
|
||||
plot.hooks.drawSeries.push(drawSeries);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'image',
|
||||
version: '1.1'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,437 +0,0 @@
|
||||
/* Flot plugin for drawing legends.
|
||||
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
var defaultOptions = {
|
||||
legend: {
|
||||
show: false,
|
||||
noColumns: 1,
|
||||
labelFormatter: null, // fn: string -> string
|
||||
container: null, // container (as jQuery object) to put legend in, null means default on top of graph
|
||||
position: 'ne', // position of default legend container within plot
|
||||
margin: 5, // distance from grid edge to default legend container within plot
|
||||
sorted: null // default to no legend sorting
|
||||
}
|
||||
};
|
||||
|
||||
function insertLegend(plot, options, placeholder, legendEntries) {
|
||||
// clear before redraw
|
||||
if (options.legend.container != null) {
|
||||
$(options.legend.container).html('');
|
||||
} else {
|
||||
placeholder.find('.legend').remove();
|
||||
}
|
||||
|
||||
if (!options.legend.show) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the legend entries in legend options
|
||||
var entries = options.legend.legendEntries = legendEntries,
|
||||
plotOffset = options.legend.plotOffset = plot.getPlotOffset(),
|
||||
html = [],
|
||||
entry, labelHtml, iconHtml,
|
||||
j = 0,
|
||||
i,
|
||||
pos = "",
|
||||
p = options.legend.position,
|
||||
m = options.legend.margin,
|
||||
shape = {
|
||||
name: '',
|
||||
label: '',
|
||||
xPos: '',
|
||||
yPos: ''
|
||||
};
|
||||
|
||||
html[j++] = '<svg class="legendLayer" style="width:inherit;height:inherit;">';
|
||||
html[j++] = '<rect class="background" width="100%" height="100%"/>';
|
||||
html[j++] = svgShapeDefs;
|
||||
|
||||
var left = 0;
|
||||
var columnWidths = [];
|
||||
var style = window.getComputedStyle(document.querySelector('body'));
|
||||
for (i = 0; i < entries.length; ++i) {
|
||||
var columnIndex = i % options.legend.noColumns;
|
||||
entry = entries[i];
|
||||
shape.label = entry.label;
|
||||
var info = plot.getSurface().getTextInfo('', shape.label, {
|
||||
style: style.fontStyle,
|
||||
variant: style.fontVariant,
|
||||
weight: style.fontWeight,
|
||||
size: parseInt(style.fontSize),
|
||||
lineHeight: parseInt(style.lineHeight),
|
||||
family: style.fontFamily
|
||||
});
|
||||
|
||||
var labelWidth = info.width;
|
||||
// 36px = 1.5em + 6px margin
|
||||
var iconWidth = 48;
|
||||
if (columnWidths[columnIndex]) {
|
||||
if (labelWidth > columnWidths[columnIndex]) {
|
||||
columnWidths[columnIndex] = labelWidth + iconWidth;
|
||||
}
|
||||
} else {
|
||||
columnWidths[columnIndex] = labelWidth + iconWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate html for icons and labels from a list of entries
|
||||
for (i = 0; i < entries.length; ++i) {
|
||||
var columnIndex = i % options.legend.noColumns;
|
||||
entry = entries[i];
|
||||
iconHtml = '';
|
||||
shape.label = entry.label;
|
||||
shape.xPos = (left + 3) + 'px';
|
||||
left += columnWidths[columnIndex];
|
||||
if ((i + 1) % options.legend.noColumns === 0) {
|
||||
left = 0;
|
||||
}
|
||||
shape.yPos = Math.floor(i / options.legend.noColumns) * 1.5 + 'em';
|
||||
// area
|
||||
if (entry.options.lines.show && entry.options.lines.fill) {
|
||||
shape.name = 'area';
|
||||
shape.fillColor = entry.color;
|
||||
iconHtml += getEntryIconHtml(shape);
|
||||
}
|
||||
// bars
|
||||
if (entry.options.bars.show) {
|
||||
shape.name = 'bar';
|
||||
shape.fillColor = entry.color;
|
||||
iconHtml += getEntryIconHtml(shape);
|
||||
}
|
||||
// lines
|
||||
if (entry.options.lines.show && !entry.options.lines.fill) {
|
||||
shape.name = 'line';
|
||||
shape.strokeColor = entry.color;
|
||||
shape.strokeWidth = entry.options.lines.lineWidth;
|
||||
iconHtml += getEntryIconHtml(shape);
|
||||
}
|
||||
// points
|
||||
if (entry.options.points.show) {
|
||||
shape.name = entry.options.points.symbol;
|
||||
shape.strokeColor = entry.color;
|
||||
shape.fillColor = entry.options.points.fillColor;
|
||||
shape.strokeWidth = entry.options.points.lineWidth;
|
||||
iconHtml += getEntryIconHtml(shape);
|
||||
}
|
||||
|
||||
labelHtml = '<text x="' + shape.xPos + '" y="' + shape.yPos + '" text-anchor="start"><tspan dx="2em" dy="1.2em">' + shape.label + '</tspan></text>'
|
||||
html[j++] = '<g>' + iconHtml + labelHtml + '</g>';
|
||||
}
|
||||
|
||||
html[j++] = '</svg>';
|
||||
if (m[0] == null) {
|
||||
m = [m, m];
|
||||
}
|
||||
|
||||
if (p.charAt(0) === 'n') {
|
||||
pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
|
||||
} else if (p.charAt(0) === 's') {
|
||||
pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
|
||||
}
|
||||
|
||||
if (p.charAt(1) === 'e') {
|
||||
pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
|
||||
} else if (p.charAt(1) === 'w') {
|
||||
pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
|
||||
}
|
||||
|
||||
var width = 6;
|
||||
for (i = 0; i < columnWidths.length; ++i) {
|
||||
width += columnWidths[i];
|
||||
}
|
||||
|
||||
var legendEl,
|
||||
height = Math.ceil(entries.length / options.legend.noColumns) * 1.6;
|
||||
if (!options.legend.container) {
|
||||
legendEl = $('<div class="legend" style="position:absolute;' + pos + '">' + html.join('') + '</div>').appendTo(placeholder);
|
||||
legendEl.css('width', width + 'px');
|
||||
legendEl.css('height', height + 'em');
|
||||
legendEl.css('pointerEvents', 'none');
|
||||
} else {
|
||||
legendEl = $(html.join('')).appendTo(options.legend.container)[0];
|
||||
options.legend.container.style.width = width + 'px';
|
||||
options.legend.container.style.height = height + 'em';
|
||||
}
|
||||
}
|
||||
|
||||
// Generate html for a shape
|
||||
function getEntryIconHtml(shape) {
|
||||
var html = '',
|
||||
name = shape.name,
|
||||
x = shape.xPos,
|
||||
y = shape.yPos,
|
||||
fill = shape.fillColor,
|
||||
stroke = shape.strokeColor,
|
||||
width = shape.strokeWidth;
|
||||
switch (name) {
|
||||
case 'circle':
|
||||
html = '<use xlink:href="#circle" class="legendIcon" ' +
|
||||
'x="' + x + '" ' +
|
||||
'y="' + y + '" ' +
|
||||
'fill="' + fill + '" ' +
|
||||
'stroke="' + stroke + '" ' +
|
||||
'stroke-width="' + width + '" ' +
|
||||
'width="1.5em" height="1.5em"' +
|
||||
'/>';
|
||||
break;
|
||||
case 'diamond':
|
||||
html = '<use xlink:href="#diamond" class="legendIcon" ' +
|
||||
'x="' + x + '" ' +
|
||||
'y="' + y + '" ' +
|
||||
'fill="' + fill + '" ' +
|
||||
'stroke="' + stroke + '" ' +
|
||||
'stroke-width="' + width + '" ' +
|
||||
'width="1.5em" height="1.5em"' +
|
||||
'/>';
|
||||
break;
|
||||
case 'cross':
|
||||
html = '<use xlink:href="#cross" class="legendIcon" ' +
|
||||
'x="' + x + '" ' +
|
||||
'y="' + y + '" ' +
|
||||
// 'fill="' + fill + '" ' +
|
||||
'stroke="' + stroke + '" ' +
|
||||
'stroke-width="' + width + '" ' +
|
||||
'width="1.5em" height="1.5em"' +
|
||||
'/>';
|
||||
break;
|
||||
case 'rectangle':
|
||||
html = '<use xlink:href="#rectangle" class="legendIcon" ' +
|
||||
'x="' + x + '" ' +
|
||||
'y="' + y + '" ' +
|
||||
'fill="' + fill + '" ' +
|
||||
'stroke="' + stroke + '" ' +
|
||||
'stroke-width="' + width + '" ' +
|
||||
'width="1.5em" height="1.5em"' +
|
||||
'/>';
|
||||
break;
|
||||
case 'plus':
|
||||
html = '<use xlink:href="#plus" class="legendIcon" ' +
|
||||
'x="' + x + '" ' +
|
||||
'y="' + y + '" ' +
|
||||
// 'fill="' + fill + '" ' +
|
||||
'stroke="' + stroke + '" ' +
|
||||
'stroke-width="' + width + '" ' +
|
||||
'width="1.5em" height="1.5em"' +
|
||||
'/>';
|
||||
break;
|
||||
case 'bar':
|
||||
html = '<use xlink:href="#bars" class="legendIcon" ' +
|
||||
'x="' + x + '" ' +
|
||||
'y="' + y + '" ' +
|
||||
'fill="' + fill + '" ' +
|
||||
// 'stroke="' + stroke + '" ' +
|
||||
// 'stroke-width="' + width + '" ' +
|
||||
'width="1.5em" height="1.5em"' +
|
||||
'/>';
|
||||
break;
|
||||
case 'area':
|
||||
html = '<use xlink:href="#area" class="legendIcon" ' +
|
||||
'x="' + x + '" ' +
|
||||
'y="' + y + '" ' +
|
||||
'fill="' + fill + '" ' +
|
||||
// 'stroke="' + stroke + '" ' +
|
||||
// 'stroke-width="' + width + '" ' +
|
||||
'width="1.5em" height="1.5em"' +
|
||||
'/>';
|
||||
break;
|
||||
case 'line':
|
||||
html = '<use xlink:href="#line" class="legendIcon" ' +
|
||||
'x="' + x + '" ' +
|
||||
'y="' + y + '" ' +
|
||||
// 'fill="' + fill + '" ' +
|
||||
'stroke="' + stroke + '" ' +
|
||||
'stroke-width="' + width + '" ' +
|
||||
'width="1.5em" height="1.5em"' +
|
||||
'/>';
|
||||
break;
|
||||
default:
|
||||
// default is circle
|
||||
html = '<use xlink:href="#circle" class="legendIcon" ' +
|
||||
'x="' + x + '" ' +
|
||||
'y="' + y + '" ' +
|
||||
'fill="' + fill + '" ' +
|
||||
'stroke="' + stroke + '" ' +
|
||||
'stroke-width="' + width + '" ' +
|
||||
'width="1.5em" height="1.5em"' +
|
||||
'/>';
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Define svg symbols for shapes
|
||||
var svgShapeDefs = '' +
|
||||
'<defs>' +
|
||||
'<symbol id="line" fill="none" viewBox="-5 -5 25 25">' +
|
||||
'<polyline points="0,15 5,5 10,10 15,0"/>' +
|
||||
'</symbol>' +
|
||||
|
||||
'<symbol id="area" stroke-width="1" viewBox="-5 -5 25 25">' +
|
||||
'<polyline points="0,15 5,5 10,10 15,0, 15,15, 0,15"/>' +
|
||||
'</symbol>' +
|
||||
|
||||
'<symbol id="bars" stroke-width="1" viewBox="-5 -5 25 25">' +
|
||||
'<polyline points="1.5,15.5 1.5,12.5, 4.5,12.5 4.5,15.5 6.5,15.5 6.5,3.5, 9.5,3.5 9.5,15.5 11.5,15.5 11.5,7.5 14.5,7.5 14.5,15.5 1.5,15.5"/>' +
|
||||
'</symbol>' +
|
||||
|
||||
'<symbol id="circle" viewBox="-5 -5 25 25">' +
|
||||
'<circle cx="0" cy="15" r="2.5"/>' +
|
||||
'<circle cx="5" cy="5" r="2.5"/>' +
|
||||
'<circle cx="10" cy="10" r="2.5"/>' +
|
||||
'<circle cx="15" cy="0" r="2.5"/>' +
|
||||
'</symbol>' +
|
||||
|
||||
'<symbol id="rectangle" viewBox="-5 -5 25 25">' +
|
||||
'<rect x="-2.1" y="12.9" width="4.2" height="4.2"/>' +
|
||||
'<rect x="2.9" y="2.9" width="4.2" height="4.2"/>' +
|
||||
'<rect x="7.9" y="7.9" width="4.2" height="4.2"/>' +
|
||||
'<rect x="12.9" y="-2.1" width="4.2" height="4.2"/>' +
|
||||
'</symbol>' +
|
||||
|
||||
'<symbol id="diamond" viewBox="-5 -5 25 25">' +
|
||||
'<path d="M-3,15 L0,12 L3,15, L0,18 Z"/>' +
|
||||
'<path d="M2,5 L5,2 L8,5, L5,8 Z"/>' +
|
||||
'<path d="M7,10 L10,7 L13,10, L10,13 Z"/>' +
|
||||
'<path d="M12,0 L15,-3 L18,0, L15,3 Z"/>' +
|
||||
'</symbol>' +
|
||||
|
||||
'<symbol id="cross" fill="none" viewBox="-5 -5 25 25">' +
|
||||
'<path d="M-2.1,12.9 L2.1,17.1, M2.1,12.9 L-2.1,17.1 Z"/>' +
|
||||
'<path d="M2.9,2.9 L7.1,7.1 M7.1,2.9 L2.9,7.1 Z"/>' +
|
||||
'<path d="M7.9,7.9 L12.1,12.1 M12.1,7.9 L7.9,12.1 Z"/>' +
|
||||
'<path d="M12.9,-2.1 L17.1,2.1 M17.1,-2.1 L12.9,2.1 Z"/>' +
|
||||
'</symbol>' +
|
||||
|
||||
'<symbol id="plus" fill="none" viewBox="-5 -5 25 25">' +
|
||||
'<path d="M0,12 L0,18, M-3,15 L3,15 Z"/>' +
|
||||
'<path d="M5,2 L5,8 M2,5 L8,5 Z"/>' +
|
||||
'<path d="M10,7 L10,13 M7,10 L13,10 Z"/>' +
|
||||
'<path d="M15,-3 L15,3 M12,0 L18,0 Z"/>' +
|
||||
'</symbol>' +
|
||||
'</defs>';
|
||||
|
||||
// Generate a list of legend entries in their final order
|
||||
function getLegendEntries(series, labelFormatter, sorted) {
|
||||
var lf = labelFormatter,
|
||||
legendEntries = series.reduce(function(validEntries, s, i) {
|
||||
var labelEval = (lf ? lf(s.label, s) : s.label)
|
||||
if (s.hasOwnProperty("label") ? labelEval : true) {
|
||||
var entry = {
|
||||
label: labelEval || 'Plot ' + (i + 1),
|
||||
color: s.color,
|
||||
options: {
|
||||
lines: s.lines,
|
||||
points: s.points,
|
||||
bars: s.bars
|
||||
}
|
||||
}
|
||||
validEntries.push(entry)
|
||||
}
|
||||
return validEntries;
|
||||
}, []);
|
||||
|
||||
// Sort the legend using either the default or a custom comparator
|
||||
if (sorted) {
|
||||
if ($.isFunction(sorted)) {
|
||||
legendEntries.sort(sorted);
|
||||
} else if (sorted === 'reverse') {
|
||||
legendEntries.reverse();
|
||||
} else {
|
||||
var ascending = (sorted !== 'descending');
|
||||
legendEntries.sort(function(a, b) {
|
||||
return a.label === b.label
|
||||
? 0
|
||||
: ((a.label < b.label) !== ascending ? 1 : -1 // Logical XOR
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return legendEntries;
|
||||
}
|
||||
|
||||
// return false if opts1 same as opts2
|
||||
function checkOptions(opts1, opts2) {
|
||||
for (var prop in opts1) {
|
||||
if (opts1.hasOwnProperty(prop)) {
|
||||
if (opts1[prop] !== opts2[prop]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare two lists of legend entries
|
||||
function shouldRedraw(oldEntries, newEntries) {
|
||||
if (!oldEntries || !newEntries) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (oldEntries.length !== newEntries.length) {
|
||||
return true;
|
||||
}
|
||||
var i, newEntry, oldEntry, newOpts, oldOpts;
|
||||
for (i = 0; i < newEntries.length; i++) {
|
||||
newEntry = newEntries[i];
|
||||
oldEntry = oldEntries[i];
|
||||
|
||||
if (newEntry.label !== oldEntry.label) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (newEntry.color !== oldEntry.color) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for changes in lines options
|
||||
newOpts = newEntry.options.lines;
|
||||
oldOpts = oldEntry.options.lines;
|
||||
if (checkOptions(newOpts, oldOpts)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for changes in points options
|
||||
newOpts = newEntry.options.points;
|
||||
oldOpts = oldEntry.options.points;
|
||||
if (checkOptions(newOpts, oldOpts)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for changes in bars options
|
||||
newOpts = newEntry.options.bars;
|
||||
oldOpts = oldEntry.options.bars;
|
||||
if (checkOptions(newOpts, oldOpts)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function init(plot) {
|
||||
plot.hooks.setupGrid.push(function (plot) {
|
||||
var options = plot.getOptions();
|
||||
var series = plot.getData(),
|
||||
labelFormatter = options.legend.labelFormatter,
|
||||
oldEntries = options.legend.legendEntries,
|
||||
oldPlotOffset = options.legend.plotOffset,
|
||||
newEntries = getLegendEntries(series, labelFormatter, options.legend.sorted),
|
||||
newPlotOffset = plot.getPlotOffset();
|
||||
|
||||
if (shouldRedraw(oldEntries, newEntries) ||
|
||||
checkOptions(oldPlotOffset, newPlotOffset)) {
|
||||
insertLegend(plot, options, plot.getPlaceholder(), newEntries);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: defaultOptions,
|
||||
name: 'legend',
|
||||
version: '1.0'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,298 +0,0 @@
|
||||
/* Pretty handling of log axes.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Copyright (c) 2015 Ciprian Ceteras cipix2000@gmail.com.
|
||||
Copyright (c) 2017 Raluca Portase
|
||||
Licensed under the MIT license.
|
||||
|
||||
Set axis.mode to "log" to enable.
|
||||
*/
|
||||
|
||||
/* global jQuery*/
|
||||
|
||||
/**
|
||||
## jquery.flot.logaxis
|
||||
This plugin is used to create logarithmic axis. This includes tick generation,
|
||||
formatters and transformers to and from logarithmic representation.
|
||||
|
||||
### Methods and hooks
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
var options = {
|
||||
xaxis: {}
|
||||
};
|
||||
|
||||
/*tick generators and formatters*/
|
||||
var PREFERRED_LOG_TICK_VALUES = computePreferedLogTickValues(Number.MAX_VALUE, 10),
|
||||
EXTENDED_LOG_TICK_VALUES = computePreferedLogTickValues(Number.MAX_VALUE, 4);
|
||||
|
||||
function computePreferedLogTickValues(endLimit, rangeStep) {
|
||||
var log10End = Math.floor(Math.log(endLimit) * Math.LOG10E) - 1,
|
||||
log10Start = -log10End,
|
||||
val, range, vals = [];
|
||||
|
||||
for (var power = log10Start; power <= log10End; power++) {
|
||||
range = parseFloat('1e' + power);
|
||||
for (var mult = 1; mult < 9; mult += rangeStep) {
|
||||
val = range * mult;
|
||||
vals.push(val);
|
||||
}
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
/**
|
||||
- logTickGenerator(plot, axis, noTicks)
|
||||
|
||||
Generates logarithmic ticks, depending on axis range.
|
||||
In case the number of ticks that can be generated is less than the expected noTicks/4,
|
||||
a linear tick generation is used.
|
||||
*/
|
||||
var logTickGenerator = function (plot, axis, noTicks) {
|
||||
var ticks = [],
|
||||
minIdx = -1,
|
||||
maxIdx = -1,
|
||||
surface = plot.getCanvas(),
|
||||
logTickValues = PREFERRED_LOG_TICK_VALUES,
|
||||
min = clampAxis(axis, plot),
|
||||
max = axis.max;
|
||||
|
||||
if (!noTicks) {
|
||||
noTicks = 0.3 * Math.sqrt(axis.direction === "x" ? surface.width : surface.height);
|
||||
}
|
||||
|
||||
PREFERRED_LOG_TICK_VALUES.some(function (val, i) {
|
||||
if (val >= min) {
|
||||
minIdx = i;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
PREFERRED_LOG_TICK_VALUES.some(function (val, i) {
|
||||
if (val >= max) {
|
||||
maxIdx = i;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (maxIdx === -1) {
|
||||
maxIdx = PREFERRED_LOG_TICK_VALUES.length - 1;
|
||||
}
|
||||
|
||||
if (maxIdx - minIdx <= noTicks / 4 && logTickValues.length !== EXTENDED_LOG_TICK_VALUES.length) {
|
||||
//try with multiple of 5 for tick values
|
||||
logTickValues = EXTENDED_LOG_TICK_VALUES;
|
||||
minIdx *= 2;
|
||||
maxIdx *= 2;
|
||||
}
|
||||
|
||||
var lastDisplayed = null,
|
||||
inverseNoTicks = 1 / noTicks,
|
||||
tickValue, pixelCoord, tick;
|
||||
|
||||
// Count the number of tick values would appear, if we can get at least
|
||||
// nTicks / 4 accept them.
|
||||
if (maxIdx - minIdx >= noTicks / 4) {
|
||||
for (var idx = maxIdx; idx >= minIdx; idx--) {
|
||||
tickValue = logTickValues[idx];
|
||||
pixelCoord = (Math.log(tickValue) - Math.log(min)) / (Math.log(max) - Math.log(min));
|
||||
tick = tickValue;
|
||||
|
||||
if (lastDisplayed === null) {
|
||||
lastDisplayed = {
|
||||
pixelCoord: pixelCoord,
|
||||
idealPixelCoord: pixelCoord
|
||||
};
|
||||
} else {
|
||||
if (Math.abs(pixelCoord - lastDisplayed.pixelCoord) >= inverseNoTicks) {
|
||||
lastDisplayed = {
|
||||
pixelCoord: pixelCoord,
|
||||
idealPixelCoord: lastDisplayed.idealPixelCoord - inverseNoTicks
|
||||
};
|
||||
} else {
|
||||
tick = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (tick) {
|
||||
ticks.push(tick);
|
||||
}
|
||||
}
|
||||
// Since we went in backwards order.
|
||||
ticks.reverse();
|
||||
} else {
|
||||
var tickSize = plot.computeTickSize(min, max, noTicks),
|
||||
customAxis = {min: min, max: max, tickSize: tickSize};
|
||||
ticks = $.plot.linearTickGenerator(customAxis);
|
||||
}
|
||||
|
||||
return ticks;
|
||||
};
|
||||
|
||||
var clampAxis = function (axis, plot) {
|
||||
var min = axis.min,
|
||||
max = axis.max;
|
||||
|
||||
if (min <= 0) {
|
||||
//for empty graph if axis.min is not strictly positive make it 0.1
|
||||
if (axis.datamin === null) {
|
||||
min = axis.min = 0.1;
|
||||
} else {
|
||||
min = processAxisOffset(plot, axis);
|
||||
}
|
||||
|
||||
if (max < min) {
|
||||
axis.max = axis.datamax !== null ? axis.datamax : axis.options.max;
|
||||
axis.options.offset.below = 0;
|
||||
axis.options.offset.above = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
- logTickFormatter(value, axis, precision)
|
||||
|
||||
This is the corresponding tickFormatter of the logaxis.
|
||||
For a number greater that 10^6 or smaller than 10^(-3), this will be drawn
|
||||
with e representation
|
||||
*/
|
||||
var logTickFormatter = function (value, axis, precision) {
|
||||
var tenExponent = value > 0 ? Math.floor(Math.log(value) / Math.LN10) : 0;
|
||||
|
||||
if (precision) {
|
||||
if ((tenExponent >= -4) && (tenExponent <= 7)) {
|
||||
return $.plot.defaultTickFormatter(value, axis, precision);
|
||||
} else {
|
||||
return $.plot.expRepTickFormatter(value, axis, precision);
|
||||
}
|
||||
}
|
||||
if ((tenExponent >= -4) && (tenExponent <= 7)) {
|
||||
//if we have float numbers, return a limited length string(ex: 0.0009 is represented as 0.000900001)
|
||||
var formattedValue = tenExponent < 0 ? value.toFixed(-tenExponent) : value.toFixed(tenExponent + 2);
|
||||
if (formattedValue.indexOf('.') !== -1) {
|
||||
var lastZero = formattedValue.lastIndexOf('0');
|
||||
|
||||
while (lastZero === formattedValue.length - 1) {
|
||||
formattedValue = formattedValue.slice(0, -1);
|
||||
lastZero = formattedValue.lastIndexOf('0');
|
||||
}
|
||||
|
||||
//delete the dot if is last
|
||||
if (formattedValue.indexOf('.') === formattedValue.length - 1) {
|
||||
formattedValue = formattedValue.slice(0, -1);
|
||||
}
|
||||
}
|
||||
return formattedValue;
|
||||
} else {
|
||||
return $.plot.expRepTickFormatter(value, axis);
|
||||
}
|
||||
};
|
||||
|
||||
/*logaxis caracteristic functions*/
|
||||
var logTransform = function (v) {
|
||||
if (v < PREFERRED_LOG_TICK_VALUES[0]) {
|
||||
v = PREFERRED_LOG_TICK_VALUES[0];
|
||||
}
|
||||
|
||||
return Math.log(v);
|
||||
};
|
||||
|
||||
var logInverseTransform = function (v) {
|
||||
return Math.exp(v);
|
||||
};
|
||||
|
||||
var invertedTransform = function (v) {
|
||||
return -v;
|
||||
}
|
||||
|
||||
var invertedLogTransform = function (v) {
|
||||
return -logTransform(v);
|
||||
}
|
||||
|
||||
var invertedLogInverseTransform = function (v) {
|
||||
return logInverseTransform(-v);
|
||||
}
|
||||
|
||||
/**
|
||||
- setDataminRange(plot, axis)
|
||||
|
||||
It is used for clamping the starting point of a logarithmic axis.
|
||||
This will set the axis datamin range to 0.1 or to the first datapoint greater then 0.
|
||||
The function is usefull since the logarithmic representation can not show
|
||||
values less than or equal to 0.
|
||||
*/
|
||||
function setDataminRange(plot, axis) {
|
||||
if (axis.options.mode === 'log' && axis.datamin <= 0) {
|
||||
if (axis.datamin === null) {
|
||||
axis.datamin = 0.1;
|
||||
} else {
|
||||
axis.datamin = processAxisOffset(plot, axis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processAxisOffset(plot, axis) {
|
||||
var series = plot.getData(),
|
||||
range = series
|
||||
.filter(function(series) {
|
||||
return series.xaxis === axis || series.yaxis === axis;
|
||||
})
|
||||
.map(function(series) {
|
||||
return plot.computeRangeForDataSeries(series, null, isValid);
|
||||
}),
|
||||
min = axis.direction === 'x'
|
||||
? Math.min(0.1, range && range[0] ? range[0].xmin : 0.1)
|
||||
: Math.min(0.1, range && range[0] ? range[0].ymin : 0.1);
|
||||
|
||||
axis.min = min;
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
function isValid(a) {
|
||||
return a > 0;
|
||||
}
|
||||
|
||||
function init(plot) {
|
||||
plot.hooks.processOptions.push(function (plot) {
|
||||
$.each(plot.getAxes(), function (axisName, axis) {
|
||||
var opts = axis.options;
|
||||
if (opts.mode === 'log') {
|
||||
axis.tickGenerator = function (axis) {
|
||||
var noTicks = 11;
|
||||
return logTickGenerator(plot, axis, noTicks);
|
||||
};
|
||||
if (typeof axis.options.tickFormatter !== 'function') {
|
||||
axis.options.tickFormatter = logTickFormatter;
|
||||
}
|
||||
axis.options.transform = opts.inverted ? invertedLogTransform : logTransform;
|
||||
axis.options.inverseTransform = opts.inverted ? invertedLogInverseTransform : logInverseTransform;
|
||||
axis.options.autoScaleMargin = 0;
|
||||
plot.hooks.setRange.push(setDataminRange);
|
||||
} else if (opts.inverted) {
|
||||
axis.options.transform = invertedTransform;
|
||||
axis.options.inverseTransform = invertedTransform;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'log',
|
||||
version: '0.1'
|
||||
});
|
||||
|
||||
$.plot.logTicksGenerator = logTickGenerator;
|
||||
$.plot.logTickFormatter = logTickFormatter;
|
||||
})(jQuery);
|
||||
@@ -1,798 +0,0 @@
|
||||
/* Flot plugin for adding the ability to pan and zoom the plot.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Copyright (c) 2016 Ciprian Ceteras.
|
||||
Copyright (c) 2017 Raluca Portase.
|
||||
Licensed under the MIT license.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
## jquery.flot.navigate.js
|
||||
|
||||
This flot plugin is used for adding the ability to pan and zoom the plot.
|
||||
A higher level overview is available at [interactions](interactions.md) documentation.
|
||||
|
||||
The default behaviour is scrollwheel up/down to zoom in, drag
|
||||
to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and
|
||||
plot.pan( offset ) so you easily can add custom controls. It also fires
|
||||
"plotpan" and "plotzoom" events, useful for synchronizing plots.
|
||||
|
||||
The plugin supports these options:
|
||||
```js
|
||||
zoom: {
|
||||
interactive: false,
|
||||
active: false,
|
||||
amount: 1.5 // 2 = 200% (zoom in), 0.5 = 50% (zoom out)
|
||||
}
|
||||
|
||||
pan: {
|
||||
interactive: false,
|
||||
active: false,
|
||||
cursor: "move", // CSS mouse cursor value used when dragging, e.g. "pointer"
|
||||
frameRate: 60,
|
||||
mode: "smart" // enable smart pan mode
|
||||
}
|
||||
|
||||
xaxis: {
|
||||
axisZoom: true, //zoom axis when mouse over it is allowed
|
||||
plotZoom: true, //zoom axis is allowed for plot zoom
|
||||
axisPan: true, //pan axis when mouse over it is allowed
|
||||
plotPan: true //pan axis is allowed for plot pan
|
||||
}
|
||||
|
||||
yaxis: {
|
||||
axisZoom: true, //zoom axis when mouse over it is allowed
|
||||
plotZoom: true, //zoom axis is allowed for plot zoom
|
||||
axisPan: true, //pan axis when mouse over it is allowed
|
||||
plotPan: true //pan axis is allowed for plot pan
|
||||
}
|
||||
```
|
||||
**interactive** enables the built-in drag/click behaviour. If you enable
|
||||
interactive for pan, then you'll have a basic plot that supports moving
|
||||
around; the same for zoom.
|
||||
|
||||
**active** is true after a touch tap on plot. This enables plot navigation.
|
||||
Once activated, zoom and pan cannot be deactivated. When the plot becomes active,
|
||||
"plotactivated" event is triggered.
|
||||
|
||||
**amount** specifies the default amount to zoom in (so 1.5 = 150%) relative to
|
||||
the current viewport.
|
||||
|
||||
**cursor** is a standard CSS mouse cursor string used for visual feedback to the
|
||||
user when dragging.
|
||||
|
||||
**frameRate** specifies the maximum number of times per second the plot will
|
||||
update itself while the user is panning around on it (set to null to disable
|
||||
intermediate pans, the plot will then not update until the mouse button is
|
||||
released).
|
||||
|
||||
**mode** a string specifies the pan mode for mouse interaction. Accepted values:
|
||||
'manual': no pan hint or direction snapping;
|
||||
'smart': The graph shows pan hint bar and the pan movement will snap
|
||||
to one direction when the drag direction is close to it;
|
||||
'smartLock'. The graph shows pan hint bar and the pan movement will always
|
||||
snap to a direction that the drag diorection started with.
|
||||
|
||||
Example API usage:
|
||||
```js
|
||||
plot = $.plot(...);
|
||||
|
||||
// zoom default amount in on the pixel ( 10, 20 )
|
||||
plot.zoom({ center: { left: 10, top: 20 } });
|
||||
|
||||
// zoom out again
|
||||
plot.zoomOut({ center: { left: 10, top: 20 } });
|
||||
|
||||
// zoom 200% in on the pixel (10, 20)
|
||||
plot.zoom({ amount: 2, center: { left: 10, top: 20 } });
|
||||
|
||||
// pan 100 pixels to the left (changing x-range in a positive way) and 20 down
|
||||
plot.pan({ left: -100, top: 20 })
|
||||
```
|
||||
|
||||
Here, "center" specifies where the center of the zooming should happen. Note
|
||||
that this is defined in pixel space, not the space of the data points (you can
|
||||
use the p2c helpers on the axes in Flot to help you convert between these).
|
||||
|
||||
**amount** is the amount to zoom the viewport relative to the current range, so
|
||||
1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You
|
||||
can set the default in the options.
|
||||
*/
|
||||
|
||||
/* eslint-enable */
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var options = {
|
||||
zoom: {
|
||||
interactive: false,
|
||||
active: false,
|
||||
amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out)
|
||||
},
|
||||
pan: {
|
||||
interactive: false,
|
||||
active: false,
|
||||
cursor: "move",
|
||||
frameRate: 60,
|
||||
mode: 'smart'
|
||||
},
|
||||
recenter: {
|
||||
interactive: true
|
||||
},
|
||||
xaxis: {
|
||||
axisZoom: true, //zoom axis when mouse over it is allowed
|
||||
plotZoom: true, //zoom axis is allowed for plot zoom
|
||||
axisPan: true, //pan axis when mouse over it is allowed
|
||||
plotPan: true //pan axis is allowed for plot pan
|
||||
},
|
||||
yaxis: {
|
||||
axisZoom: true,
|
||||
plotZoom: true,
|
||||
axisPan: true,
|
||||
plotPan: true
|
||||
}
|
||||
};
|
||||
|
||||
var saturated = $.plot.saturated;
|
||||
var browser = $.plot.browser;
|
||||
var SNAPPING_CONSTANT = $.plot.uiConstants.SNAPPING_CONSTANT;
|
||||
var PANHINT_LENGTH_CONSTANT = $.plot.uiConstants.PANHINT_LENGTH_CONSTANT;
|
||||
|
||||
function init(plot) {
|
||||
plot.hooks.processOptions.push(initNevigation);
|
||||
}
|
||||
|
||||
function initNevigation(plot, options) {
|
||||
var panAxes = null;
|
||||
var canDrag = false;
|
||||
var useManualPan = options.pan.mode === 'manual',
|
||||
smartPanLock = options.pan.mode === 'smartLock',
|
||||
useSmartPan = smartPanLock || options.pan.mode === 'smart';
|
||||
|
||||
function onZoomClick(e, zoomOut, amount) {
|
||||
var page = browser.getPageXY(e);
|
||||
|
||||
var c = plot.offset();
|
||||
c.left = page.X - c.left;
|
||||
c.top = page.Y - c.top;
|
||||
|
||||
var ec = plot.getPlaceholder().offset();
|
||||
ec.left = page.X - ec.left;
|
||||
ec.top = page.Y - ec.top;
|
||||
|
||||
var axes = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {
|
||||
var box = axis.box;
|
||||
if (box !== undefined) {
|
||||
return (ec.left > box.left) && (ec.left < box.left + box.width) &&
|
||||
(ec.top > box.top) && (ec.top < box.top + box.height);
|
||||
}
|
||||
});
|
||||
|
||||
if (axes.length === 0) {
|
||||
axes = undefined;
|
||||
}
|
||||
|
||||
if (zoomOut) {
|
||||
plot.zoomOut({
|
||||
center: c,
|
||||
axes: axes,
|
||||
amount: amount
|
||||
});
|
||||
} else {
|
||||
plot.zoom({
|
||||
center: c,
|
||||
axes: axes,
|
||||
amount: amount
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var prevCursor = 'default',
|
||||
panHint = null,
|
||||
panTimeout = null,
|
||||
plotState,
|
||||
prevDragPosition = { x: 0, y: 0 },
|
||||
isPanAction = false;
|
||||
|
||||
function onMouseWheel(e, delta) {
|
||||
var maxAbsoluteDeltaOnMac = 1,
|
||||
isMacScroll = Math.abs(e.originalEvent.deltaY) <= maxAbsoluteDeltaOnMac,
|
||||
defaultNonMacScrollAmount = null,
|
||||
macMagicRatio = 50,
|
||||
amount = isMacScroll ? 1 + Math.abs(e.originalEvent.deltaY) / macMagicRatio : defaultNonMacScrollAmount;
|
||||
|
||||
if (isPanAction) {
|
||||
onDragEnd(e);
|
||||
}
|
||||
|
||||
if (plot.getOptions().zoom.active) {
|
||||
e.preventDefault();
|
||||
onZoomClick(e, delta < 0, amount);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
plot.navigationState = function(startPageX, startPageY) {
|
||||
var axes = this.getAxes();
|
||||
var result = {};
|
||||
Object.keys(axes).forEach(function(axisName) {
|
||||
var axis = axes[axisName];
|
||||
result[axisName] = {
|
||||
navigationOffset: { below: axis.options.offset.below || 0,
|
||||
above: axis.options.offset.above || 0},
|
||||
axisMin: axis.min,
|
||||
axisMax: axis.max,
|
||||
diagMode: false
|
||||
}
|
||||
});
|
||||
|
||||
result.startPageX = startPageX || 0;
|
||||
result.startPageY = startPageY || 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
function onMouseDown(e) {
|
||||
canDrag = true;
|
||||
}
|
||||
|
||||
function onMouseUp(e) {
|
||||
canDrag = false;
|
||||
}
|
||||
|
||||
function isLeftMouseButtonPressed(e) {
|
||||
return e.button === 0;
|
||||
}
|
||||
|
||||
function onDragStart(e) {
|
||||
if (!canDrag || !isLeftMouseButtonPressed(e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
isPanAction = true;
|
||||
var page = browser.getPageXY(e);
|
||||
|
||||
var ec = plot.getPlaceholder().offset();
|
||||
ec.left = page.X - ec.left;
|
||||
ec.top = page.Y - ec.top;
|
||||
|
||||
panAxes = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {
|
||||
var box = axis.box;
|
||||
if (box !== undefined) {
|
||||
return (ec.left > box.left) && (ec.left < box.left + box.width) &&
|
||||
(ec.top > box.top) && (ec.top < box.top + box.height);
|
||||
}
|
||||
});
|
||||
|
||||
if (panAxes.length === 0) {
|
||||
panAxes = undefined;
|
||||
}
|
||||
|
||||
var c = plot.getPlaceholder().css('cursor');
|
||||
if (c) {
|
||||
prevCursor = c;
|
||||
}
|
||||
|
||||
plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor);
|
||||
|
||||
if (useSmartPan) {
|
||||
plotState = plot.navigationState(page.X, page.Y);
|
||||
} else if (useManualPan) {
|
||||
prevDragPosition.x = page.X;
|
||||
prevDragPosition.y = page.Y;
|
||||
}
|
||||
}
|
||||
|
||||
function onDrag(e) {
|
||||
if (!isPanAction) {
|
||||
return;
|
||||
}
|
||||
|
||||
var page = browser.getPageXY(e);
|
||||
var frameRate = plot.getOptions().pan.frameRate;
|
||||
|
||||
if (frameRate === -1) {
|
||||
if (useSmartPan) {
|
||||
plot.smartPan({
|
||||
x: plotState.startPageX - page.X,
|
||||
y: plotState.startPageY - page.Y
|
||||
}, plotState, panAxes, false, smartPanLock);
|
||||
} else if (useManualPan) {
|
||||
plot.pan({
|
||||
left: prevDragPosition.x - page.X,
|
||||
top: prevDragPosition.y - page.Y,
|
||||
axes: panAxes
|
||||
});
|
||||
prevDragPosition.x = page.X;
|
||||
prevDragPosition.y = page.Y;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (panTimeout || !frameRate) return;
|
||||
|
||||
panTimeout = setTimeout(function() {
|
||||
if (useSmartPan) {
|
||||
plot.smartPan({
|
||||
x: plotState.startPageX - page.X,
|
||||
y: plotState.startPageY - page.Y
|
||||
}, plotState, panAxes, false, smartPanLock);
|
||||
} else if (useManualPan) {
|
||||
plot.pan({
|
||||
left: prevDragPosition.x - page.X,
|
||||
top: prevDragPosition.y - page.Y,
|
||||
axes: panAxes
|
||||
});
|
||||
prevDragPosition.x = page.X;
|
||||
prevDragPosition.y = page.Y;
|
||||
}
|
||||
|
||||
panTimeout = null;
|
||||
}, 1 / frameRate * 1000);
|
||||
}
|
||||
|
||||
function onDragEnd(e) {
|
||||
if (!isPanAction) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (panTimeout) {
|
||||
clearTimeout(panTimeout);
|
||||
panTimeout = null;
|
||||
}
|
||||
|
||||
isPanAction = false;
|
||||
var page = browser.getPageXY(e);
|
||||
|
||||
plot.getPlaceholder().css('cursor', prevCursor);
|
||||
|
||||
if (useSmartPan) {
|
||||
plot.smartPan({
|
||||
x: plotState.startPageX - page.X,
|
||||
y: plotState.startPageY - page.Y
|
||||
}, plotState, panAxes, false, smartPanLock);
|
||||
plot.smartPan.end();
|
||||
} else if (useManualPan) {
|
||||
plot.pan({
|
||||
left: prevDragPosition.x - page.X,
|
||||
top: prevDragPosition.y - page.Y,
|
||||
axes: panAxes
|
||||
});
|
||||
prevDragPosition.x = 0;
|
||||
prevDragPosition.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function onDblClick(e) {
|
||||
plot.activate();
|
||||
var o = plot.getOptions()
|
||||
|
||||
if (!o.recenter.interactive) { return; }
|
||||
|
||||
var axes = plot.getTouchedAxis(e.clientX, e.clientY),
|
||||
event;
|
||||
|
||||
plot.recenter({ axes: axes[0] ? axes : null });
|
||||
|
||||
if (axes[0]) {
|
||||
event = new $.Event('re-center', { detail: {
|
||||
axisTouched: axes[0]
|
||||
}});
|
||||
} else {
|
||||
event = new $.Event('re-center', { detail: e });
|
||||
}
|
||||
plot.getPlaceholder().trigger(event);
|
||||
}
|
||||
|
||||
function onClick(e) {
|
||||
plot.activate();
|
||||
|
||||
if (isPanAction) {
|
||||
onDragEnd(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
plot.activate = function() {
|
||||
var o = plot.getOptions();
|
||||
if (!o.pan.active || !o.zoom.active) {
|
||||
o.pan.active = true;
|
||||
o.zoom.active = true;
|
||||
plot.getPlaceholder().trigger("plotactivated", [plot]);
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents(plot, eventHolder) {
|
||||
var o = plot.getOptions();
|
||||
if (o.zoom.interactive) {
|
||||
eventHolder.mousewheel(onMouseWheel);
|
||||
}
|
||||
|
||||
if (o.pan.interactive) {
|
||||
plot.addEventHandler("dragstart", onDragStart, eventHolder, 0);
|
||||
plot.addEventHandler("drag", onDrag, eventHolder, 0);
|
||||
plot.addEventHandler("dragend", onDragEnd, eventHolder, 0);
|
||||
eventHolder.bind("mousedown", onMouseDown);
|
||||
eventHolder.bind("mouseup", onMouseUp);
|
||||
}
|
||||
|
||||
eventHolder.dblclick(onDblClick);
|
||||
eventHolder.click(onClick);
|
||||
}
|
||||
|
||||
plot.zoomOut = function(args) {
|
||||
if (!args) {
|
||||
args = {};
|
||||
}
|
||||
|
||||
if (!args.amount) {
|
||||
args.amount = plot.getOptions().zoom.amount;
|
||||
}
|
||||
|
||||
args.amount = 1 / args.amount;
|
||||
plot.zoom(args);
|
||||
};
|
||||
|
||||
plot.zoom = function(args) {
|
||||
if (!args) {
|
||||
args = {};
|
||||
}
|
||||
|
||||
var c = args.center,
|
||||
amount = args.amount || plot.getOptions().zoom.amount,
|
||||
w = plot.width(),
|
||||
h = plot.height(),
|
||||
axes = args.axes || plot.getAxes();
|
||||
|
||||
if (!c) {
|
||||
c = {
|
||||
left: w / 2,
|
||||
top: h / 2
|
||||
};
|
||||
}
|
||||
|
||||
var xf = c.left / w,
|
||||
yf = c.top / h,
|
||||
minmax = {
|
||||
x: {
|
||||
min: c.left - xf * w / amount,
|
||||
max: c.left + (1 - xf) * w / amount
|
||||
},
|
||||
y: {
|
||||
min: c.top - yf * h / amount,
|
||||
max: c.top + (1 - yf) * h / amount
|
||||
}
|
||||
};
|
||||
|
||||
for (var key in axes) {
|
||||
if (!axes.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var axis = axes[key],
|
||||
opts = axis.options,
|
||||
min = minmax[axis.direction].min,
|
||||
max = minmax[axis.direction].max,
|
||||
navigationOffset = axis.options.offset;
|
||||
|
||||
//skip axis without axisZoom when zooming only on certain axis or axis without plotZoom for zoom on entire plot
|
||||
if ((!opts.axisZoom && args.axes) || (!args.axes && !opts.plotZoom)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
min = $.plot.saturated.saturate(axis.c2p(min));
|
||||
max = $.plot.saturated.saturate(axis.c2p(max));
|
||||
if (min > max) {
|
||||
// make sure min < max
|
||||
var tmp = min;
|
||||
min = max;
|
||||
max = tmp;
|
||||
}
|
||||
|
||||
var offsetBelow = $.plot.saturated.saturate(navigationOffset.below - (axis.min - min));
|
||||
var offsetAbove = $.plot.saturated.saturate(navigationOffset.above - (axis.max - max));
|
||||
opts.offset = { below: offsetBelow, above: offsetAbove };
|
||||
};
|
||||
|
||||
plot.setupGrid(true);
|
||||
plot.draw();
|
||||
|
||||
if (!args.preventEvent) {
|
||||
plot.getPlaceholder().trigger("plotzoom", [plot, args]);
|
||||
}
|
||||
};
|
||||
|
||||
plot.pan = function(args) {
|
||||
var delta = {
|
||||
x: +args.left,
|
||||
y: +args.top
|
||||
};
|
||||
|
||||
if (isNaN(delta.x)) delta.x = 0;
|
||||
if (isNaN(delta.y)) delta.y = 0;
|
||||
|
||||
$.each(args.axes || plot.getAxes(), function(_, axis) {
|
||||
var opts = axis.options,
|
||||
d = delta[axis.direction];
|
||||
|
||||
//skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot
|
||||
if ((!opts.axisPan && args.axes) || (!opts.plotPan && !args.axes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (d !== 0) {
|
||||
var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axis.min) + d) - axis.c2p(axis.p2c(axis.min))),
|
||||
navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axis.max) + d) - axis.c2p(axis.p2c(axis.max)));
|
||||
|
||||
if (!isFinite(navigationOffsetBelow)) {
|
||||
navigationOffsetBelow = 0;
|
||||
}
|
||||
|
||||
if (!isFinite(navigationOffsetAbove)) {
|
||||
navigationOffsetAbove = 0;
|
||||
}
|
||||
|
||||
opts.offset = {
|
||||
below: saturated.saturate(navigationOffsetBelow + (opts.offset.below || 0)),
|
||||
above: saturated.saturate(navigationOffsetAbove + (opts.offset.above || 0))
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
plot.setupGrid(true);
|
||||
plot.draw();
|
||||
if (!args.preventEvent) {
|
||||
plot.getPlaceholder().trigger("plotpan", [plot, args]);
|
||||
}
|
||||
};
|
||||
|
||||
plot.recenter = function(args) {
|
||||
$.each(args.axes || plot.getAxes(), function(_, axis) {
|
||||
if (args.axes) {
|
||||
if (this.direction === 'x') {
|
||||
axis.options.offset = { below: 0 };
|
||||
} else if (this.direction === 'y') {
|
||||
axis.options.offset = { above: 0 };
|
||||
}
|
||||
} else {
|
||||
axis.options.offset = { below: 0, above: 0 };
|
||||
}
|
||||
});
|
||||
plot.setupGrid(true);
|
||||
plot.draw();
|
||||
};
|
||||
|
||||
var shouldSnap = function(delta) {
|
||||
return (Math.abs(delta.y) < SNAPPING_CONSTANT && Math.abs(delta.x) >= SNAPPING_CONSTANT) ||
|
||||
(Math.abs(delta.x) < SNAPPING_CONSTANT && Math.abs(delta.y) >= SNAPPING_CONSTANT);
|
||||
}
|
||||
|
||||
// adjust delta so the pan action is constrained on the vertical or horizontal direction
|
||||
// it the movements in the other direction are small
|
||||
var adjustDeltaToSnap = function(delta) {
|
||||
if (Math.abs(delta.x) < SNAPPING_CONSTANT && Math.abs(delta.y) >= SNAPPING_CONSTANT) {
|
||||
return {x: 0, y: delta.y};
|
||||
}
|
||||
|
||||
if (Math.abs(delta.y) < SNAPPING_CONSTANT && Math.abs(delta.x) >= SNAPPING_CONSTANT) {
|
||||
return {x: delta.x, y: 0};
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
var lockedDirection = null;
|
||||
var lockDeltaDirection = function(delta) {
|
||||
if (!lockedDirection && Math.max(Math.abs(delta.x), Math.abs(delta.y)) >= SNAPPING_CONSTANT) {
|
||||
lockedDirection = Math.abs(delta.x) < Math.abs(delta.y) ? 'y' : 'x';
|
||||
}
|
||||
|
||||
switch (lockedDirection) {
|
||||
case 'x':
|
||||
return { x: delta.x, y: 0 };
|
||||
case 'y':
|
||||
return { x: 0, y: delta.y };
|
||||
default:
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
var isDiagonalMode = function(delta) {
|
||||
if (Math.abs(delta.x) > 0 && Math.abs(delta.y) > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var restoreAxisOffset = function(axes, initialState, delta) {
|
||||
var axis;
|
||||
Object.keys(axes).forEach(function(axisName) {
|
||||
axis = axes[axisName];
|
||||
if (delta[axis.direction] === 0) {
|
||||
axis.options.offset.below = initialState[axisName].navigationOffset.below;
|
||||
axis.options.offset.above = initialState[axisName].navigationOffset.above;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var prevDelta = { x: 0, y: 0 };
|
||||
plot.smartPan = function(delta, initialState, panAxes, preventEvent, smartLock) {
|
||||
var snap = smartLock ? true : shouldSnap(delta),
|
||||
axes = plot.getAxes(),
|
||||
opts;
|
||||
delta = smartLock ? lockDeltaDirection(delta) : adjustDeltaToSnap(delta);
|
||||
|
||||
if (isDiagonalMode(delta)) {
|
||||
initialState.diagMode = true;
|
||||
}
|
||||
|
||||
if (snap && initialState.diagMode === true) {
|
||||
initialState.diagMode = false;
|
||||
restoreAxisOffset(axes, initialState, delta);
|
||||
}
|
||||
|
||||
if (snap) {
|
||||
panHint = {
|
||||
start: {
|
||||
x: initialState.startPageX - plot.offset().left + plot.getPlotOffset().left,
|
||||
y: initialState.startPageY - plot.offset().top + plot.getPlotOffset().top
|
||||
},
|
||||
end: {
|
||||
x: initialState.startPageX - delta.x - plot.offset().left + plot.getPlotOffset().left,
|
||||
y: initialState.startPageY - delta.y - plot.offset().top + plot.getPlotOffset().top
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panHint = {
|
||||
start: {
|
||||
x: initialState.startPageX - plot.offset().left + plot.getPlotOffset().left,
|
||||
y: initialState.startPageY - plot.offset().top + plot.getPlotOffset().top
|
||||
},
|
||||
end: false
|
||||
}
|
||||
}
|
||||
|
||||
if (isNaN(delta.x)) delta.x = 0;
|
||||
if (isNaN(delta.y)) delta.y = 0;
|
||||
|
||||
if (panAxes) {
|
||||
axes = panAxes;
|
||||
}
|
||||
|
||||
var axis, axisMin, axisMax, p, d;
|
||||
Object.keys(axes).forEach(function(axisName) {
|
||||
axis = axes[axisName];
|
||||
axisMin = axis.min;
|
||||
axisMax = axis.max;
|
||||
opts = axis.options;
|
||||
|
||||
d = delta[axis.direction];
|
||||
p = prevDelta[axis.direction];
|
||||
|
||||
//skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot
|
||||
if ((!opts.axisPan && panAxes) || (!panAxes && !opts.plotPan)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (d !== 0) {
|
||||
var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axisMin) - (p - d)) - axis.c2p(axis.p2c(axisMin))),
|
||||
navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axisMax) - (p - d)) - axis.c2p(axis.p2c(axisMax)));
|
||||
|
||||
if (!isFinite(navigationOffsetBelow)) {
|
||||
navigationOffsetBelow = 0;
|
||||
}
|
||||
|
||||
if (!isFinite(navigationOffsetAbove)) {
|
||||
navigationOffsetAbove = 0;
|
||||
}
|
||||
|
||||
axis.options.offset.below = saturated.saturate(navigationOffsetBelow + (axis.options.offset.below || 0));
|
||||
axis.options.offset.above = saturated.saturate(navigationOffsetAbove + (axis.options.offset.above || 0));
|
||||
}
|
||||
});
|
||||
|
||||
prevDelta = delta;
|
||||
plot.setupGrid(true);
|
||||
plot.draw();
|
||||
|
||||
if (!preventEvent) {
|
||||
plot.getPlaceholder().trigger("plotpan", [plot, delta, panAxes, initialState]);
|
||||
}
|
||||
};
|
||||
|
||||
plot.smartPan.end = function() {
|
||||
panHint = null;
|
||||
lockedDirection = null;
|
||||
prevDelta = { x: 0, y: 0 };
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
|
||||
function shutdown(plot, eventHolder) {
|
||||
eventHolder.unbind("mousewheel", onMouseWheel);
|
||||
eventHolder.unbind("mousedown", onMouseDown);
|
||||
eventHolder.unbind("mouseup", onMouseUp);
|
||||
eventHolder.unbind("dragstart", onDragStart);
|
||||
eventHolder.unbind("drag", onDrag);
|
||||
eventHolder.unbind("dragend", onDragEnd);
|
||||
eventHolder.unbind("dblclick", onDblClick);
|
||||
eventHolder.unbind("click", onClick);
|
||||
|
||||
if (panTimeout) clearTimeout(panTimeout);
|
||||
}
|
||||
|
||||
function drawOverlay(plot, ctx) {
|
||||
if (panHint) {
|
||||
ctx.strokeStyle = 'rgba(96, 160, 208, 0.7)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineJoin = "round";
|
||||
var startx = Math.round(panHint.start.x),
|
||||
starty = Math.round(panHint.start.y),
|
||||
endx, endy;
|
||||
|
||||
if (panAxes) {
|
||||
if (panAxes[0].direction === 'x') {
|
||||
endy = Math.round(panHint.start.y);
|
||||
endx = Math.round(panHint.end.x);
|
||||
} else if (panAxes[0].direction === 'y') {
|
||||
endx = Math.round(panHint.start.x);
|
||||
endy = Math.round(panHint.end.y);
|
||||
}
|
||||
} else {
|
||||
endx = Math.round(panHint.end.x);
|
||||
endy = Math.round(panHint.end.y);
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
if (panHint.end === false) {
|
||||
ctx.moveTo(startx, starty - PANHINT_LENGTH_CONSTANT);
|
||||
ctx.lineTo(startx, starty + PANHINT_LENGTH_CONSTANT);
|
||||
|
||||
ctx.moveTo(startx + PANHINT_LENGTH_CONSTANT, starty);
|
||||
ctx.lineTo(startx - PANHINT_LENGTH_CONSTANT, starty);
|
||||
} else {
|
||||
var dirX = starty === endy;
|
||||
|
||||
ctx.moveTo(startx - (dirX ? 0 : PANHINT_LENGTH_CONSTANT), starty - (dirX ? PANHINT_LENGTH_CONSTANT : 0));
|
||||
ctx.lineTo(startx + (dirX ? 0 : PANHINT_LENGTH_CONSTANT), starty + (dirX ? PANHINT_LENGTH_CONSTANT : 0));
|
||||
|
||||
ctx.moveTo(startx, starty);
|
||||
ctx.lineTo(endx, endy);
|
||||
|
||||
ctx.moveTo(endx - (dirX ? 0 : PANHINT_LENGTH_CONSTANT), endy - (dirX ? PANHINT_LENGTH_CONSTANT : 0));
|
||||
ctx.lineTo(endx + (dirX ? 0 : PANHINT_LENGTH_CONSTANT), endy + (dirX ? PANHINT_LENGTH_CONSTANT : 0));
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
plot.getTouchedAxis = function(touchPointX, touchPointY) {
|
||||
var ec = plot.getPlaceholder().offset();
|
||||
ec.left = touchPointX - ec.left;
|
||||
ec.top = touchPointY - ec.top;
|
||||
|
||||
var axis = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {
|
||||
var box = axis.box;
|
||||
if (box !== undefined) {
|
||||
return (ec.left > box.left) && (ec.left < box.left + box.width) &&
|
||||
(ec.top > box.top) && (ec.top < box.top + box.height);
|
||||
}
|
||||
});
|
||||
|
||||
return axis;
|
||||
}
|
||||
|
||||
plot.hooks.drawOverlay.push(drawOverlay);
|
||||
plot.hooks.bindEvents.push(bindEvents);
|
||||
plot.hooks.shutdown.push(shutdown);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'navigate',
|
||||
version: '1.3'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,786 +0,0 @@
|
||||
/* Flot plugin for rendering pie charts.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The plugin assumes that each series has a single data value, and that each
|
||||
value is a positive integer or zero. Negative numbers don't make sense for a
|
||||
pie chart, and have unpredictable results. The values do NOT need to be
|
||||
passed in as percentages; the plugin will calculate the total and per-slice
|
||||
percentages internally.
|
||||
|
||||
* Created by Brian Medendorp
|
||||
|
||||
* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
|
||||
|
||||
The plugin supports these options:
|
||||
|
||||
series: {
|
||||
pie: {
|
||||
show: true/false
|
||||
radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
|
||||
innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
|
||||
startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
|
||||
tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
|
||||
offset: {
|
||||
top: integer value to move the pie up or down
|
||||
left: integer value to move the pie left or right, or 'auto'
|
||||
},
|
||||
stroke: {
|
||||
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
|
||||
width: integer pixel width of the stroke
|
||||
},
|
||||
label: {
|
||||
show: true/false, or 'auto'
|
||||
formatter: a user-defined function that modifies the text/style of the label text
|
||||
radius: 0-1 for percentage of fullsize, or a specified pixel length
|
||||
background: {
|
||||
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
|
||||
opacity: 0-1
|
||||
},
|
||||
threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
|
||||
},
|
||||
combine: {
|
||||
threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
|
||||
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
|
||||
label: any text value of what the combined slice should be labeled
|
||||
}
|
||||
highlight: {
|
||||
opacity: 0-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
More detail and specific examples can be found in the included HTML file.
|
||||
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
// Maximum redraw attempts when fitting labels within the plot
|
||||
|
||||
var REDRAW_ATTEMPTS = 10;
|
||||
|
||||
// Factor by which to shrink the pie when fitting labels within the plot
|
||||
|
||||
var REDRAW_SHRINK = 0.95;
|
||||
|
||||
function init(plot) {
|
||||
var canvas = null,
|
||||
target = null,
|
||||
options = null,
|
||||
maxRadius = null,
|
||||
centerLeft = null,
|
||||
centerTop = null,
|
||||
processed = false,
|
||||
ctx = null;
|
||||
|
||||
// interactive variables
|
||||
|
||||
var highlights = [];
|
||||
|
||||
// add hook to determine if pie plugin in enabled, and then perform necessary operations
|
||||
|
||||
plot.hooks.processOptions.push(function(plot, options) {
|
||||
if (options.series.pie.show) {
|
||||
options.grid.show = false;
|
||||
|
||||
// set labels.show
|
||||
|
||||
if (options.series.pie.label.show === "auto") {
|
||||
if (options.legend.show) {
|
||||
options.series.pie.label.show = false;
|
||||
} else {
|
||||
options.series.pie.label.show = true;
|
||||
}
|
||||
}
|
||||
|
||||
// set radius
|
||||
|
||||
if (options.series.pie.radius === "auto") {
|
||||
if (options.series.pie.label.show) {
|
||||
options.series.pie.radius = 3 / 4;
|
||||
} else {
|
||||
options.series.pie.radius = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure sane tilt
|
||||
|
||||
if (options.series.pie.tilt > 1) {
|
||||
options.series.pie.tilt = 1;
|
||||
} else if (options.series.pie.tilt < 0) {
|
||||
options.series.pie.tilt = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
plot.hooks.bindEvents.push(function(plot, eventHolder) {
|
||||
var options = plot.getOptions();
|
||||
if (options.series.pie.show) {
|
||||
if (options.grid.hoverable) {
|
||||
eventHolder.unbind("mousemove").mousemove(onMouseMove);
|
||||
}
|
||||
if (options.grid.clickable) {
|
||||
eventHolder.unbind("click").click(onClick);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
|
||||
var options = plot.getOptions();
|
||||
if (options.series.pie.show) {
|
||||
processDatapoints(plot, series, data, datapoints);
|
||||
}
|
||||
});
|
||||
|
||||
plot.hooks.drawOverlay.push(function(plot, octx) {
|
||||
var options = plot.getOptions();
|
||||
if (options.series.pie.show) {
|
||||
drawOverlay(plot, octx);
|
||||
}
|
||||
});
|
||||
|
||||
plot.hooks.draw.push(function(plot, newCtx) {
|
||||
var options = plot.getOptions();
|
||||
if (options.series.pie.show) {
|
||||
draw(plot, newCtx);
|
||||
}
|
||||
});
|
||||
|
||||
function processDatapoints(plot, series, datapoints) {
|
||||
if (!processed) {
|
||||
processed = true;
|
||||
canvas = plot.getCanvas();
|
||||
target = $(canvas).parent();
|
||||
options = plot.getOptions();
|
||||
plot.setData(combine(plot.getData()));
|
||||
}
|
||||
}
|
||||
|
||||
function combine(data) {
|
||||
var total = 0,
|
||||
combined = 0,
|
||||
numCombined = 0,
|
||||
color = options.series.pie.combine.color,
|
||||
newdata = [],
|
||||
i,
|
||||
value;
|
||||
|
||||
// Fix up the raw data from Flot, ensuring the data is numeric
|
||||
|
||||
for (i = 0; i < data.length; ++i) {
|
||||
value = data[i].data;
|
||||
|
||||
// If the data is an array, we'll assume that it's a standard
|
||||
// Flot x-y pair, and are concerned only with the second value.
|
||||
|
||||
// Note how we use the original array, rather than creating a
|
||||
// new one; this is more efficient and preserves any extra data
|
||||
// that the user may have stored in higher indexes.
|
||||
|
||||
if ($.isArray(value) && value.length === 1) {
|
||||
value = value[0];
|
||||
}
|
||||
|
||||
if ($.isArray(value)) {
|
||||
// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
|
||||
if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
|
||||
value[1] = +value[1];
|
||||
} else {
|
||||
value[1] = 0;
|
||||
}
|
||||
} else if (!isNaN(parseFloat(value)) && isFinite(value)) {
|
||||
value = [1, +value];
|
||||
} else {
|
||||
value = [1, 0];
|
||||
}
|
||||
|
||||
data[i].data = [value];
|
||||
}
|
||||
|
||||
// Sum up all the slices, so we can calculate percentages for each
|
||||
|
||||
for (i = 0; i < data.length; ++i) {
|
||||
total += data[i].data[0][1];
|
||||
}
|
||||
|
||||
// Count the number of slices with percentages below the combine
|
||||
// threshold; if it turns out to be just one, we won't combine.
|
||||
|
||||
for (i = 0; i < data.length; ++i) {
|
||||
value = data[i].data[0][1];
|
||||
if (value / total <= options.series.pie.combine.threshold) {
|
||||
combined += value;
|
||||
numCombined++;
|
||||
if (!color) {
|
||||
color = data[i].color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < data.length; ++i) {
|
||||
value = data[i].data[0][1];
|
||||
if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
|
||||
newdata.push(
|
||||
$.extend(data[i], { /* extend to allow keeping all other original data values
|
||||
and using them e.g. in labelFormatter. */
|
||||
data: [[1, value]],
|
||||
color: data[i].color,
|
||||
label: data[i].label,
|
||||
angle: value * Math.PI * 2 / total,
|
||||
percent: value / (total / 100)
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (numCombined > 1) {
|
||||
newdata.push({
|
||||
data: [[1, combined]],
|
||||
color: color,
|
||||
label: options.series.pie.combine.label,
|
||||
angle: combined * Math.PI * 2 / total,
|
||||
percent: combined / (total / 100)
|
||||
});
|
||||
}
|
||||
|
||||
return newdata;
|
||||
}
|
||||
|
||||
function draw(plot, newCtx) {
|
||||
if (!target) {
|
||||
return; // if no series were passed
|
||||
}
|
||||
|
||||
var canvasWidth = plot.getPlaceholder().width(),
|
||||
canvasHeight = plot.getPlaceholder().height(),
|
||||
legendWidth = target.children().filter(".legend").children().width() || 0;
|
||||
|
||||
ctx = newCtx;
|
||||
|
||||
// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
|
||||
|
||||
// When combining smaller slices into an 'other' slice, we need to
|
||||
// add a new series. Since Flot gives plugins no way to modify the
|
||||
// list of series, the pie plugin uses a hack where the first call
|
||||
// to processDatapoints results in a call to setData with the new
|
||||
// list of series, then subsequent processDatapoints do nothing.
|
||||
|
||||
// The plugin-global 'processed' flag is used to control this hack;
|
||||
// it starts out false, and is set to true after the first call to
|
||||
// processDatapoints.
|
||||
|
||||
// Unfortunately this turns future setData calls into no-ops; they
|
||||
// call processDatapoints, the flag is true, and nothing happens.
|
||||
|
||||
// To fix this we'll set the flag back to false here in draw, when
|
||||
// all series have been processed, so the next sequence of calls to
|
||||
// processDatapoints once again starts out with a slice-combine.
|
||||
// This is really a hack; in 0.9 we need to give plugins a proper
|
||||
// way to modify series before any processing begins.
|
||||
|
||||
processed = false;
|
||||
|
||||
// calculate maximum radius and center point
|
||||
maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
|
||||
centerTop = canvasHeight / 2 + options.series.pie.offset.top;
|
||||
centerLeft = canvasWidth / 2;
|
||||
|
||||
if (options.series.pie.offset.left === "auto") {
|
||||
if (options.legend.position.match("w")) {
|
||||
centerLeft += legendWidth / 2;
|
||||
} else {
|
||||
centerLeft -= legendWidth / 2;
|
||||
}
|
||||
if (centerLeft < maxRadius) {
|
||||
centerLeft = maxRadius;
|
||||
} else if (centerLeft > canvasWidth - maxRadius) {
|
||||
centerLeft = canvasWidth - maxRadius;
|
||||
}
|
||||
} else {
|
||||
centerLeft += options.series.pie.offset.left;
|
||||
}
|
||||
|
||||
var slices = plot.getData(),
|
||||
attempts = 0;
|
||||
|
||||
// Keep shrinking the pie's radius until drawPie returns true,
|
||||
// indicating that all the labels fit, or we try too many times.
|
||||
do {
|
||||
if (attempts > 0) {
|
||||
maxRadius *= REDRAW_SHRINK;
|
||||
}
|
||||
attempts += 1;
|
||||
clear();
|
||||
if (options.series.pie.tilt <= 0.8) {
|
||||
drawShadow();
|
||||
}
|
||||
} while (!drawPie() && attempts < REDRAW_ATTEMPTS)
|
||||
|
||||
if (attempts >= REDRAW_ATTEMPTS) {
|
||||
clear();
|
||||
target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
|
||||
}
|
||||
|
||||
if (plot.setSeries && plot.insertLegend) {
|
||||
plot.setSeries(slices);
|
||||
plot.insertLegend();
|
||||
}
|
||||
|
||||
// we're actually done at this point, just defining internal functions at this point
|
||||
function clear() {
|
||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
target.children().filter(".pieLabel, .pieLabelBackground").remove();
|
||||
}
|
||||
|
||||
function drawShadow() {
|
||||
var shadowLeft = options.series.pie.shadow.left;
|
||||
var shadowTop = options.series.pie.shadow.top;
|
||||
var edge = 10;
|
||||
var alpha = options.series.pie.shadow.alpha;
|
||||
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
|
||||
|
||||
if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
|
||||
return; // shadow would be outside canvas, so don't draw it
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(shadowLeft, shadowTop);
|
||||
ctx.globalAlpha = alpha;
|
||||
ctx.fillStyle = "#000";
|
||||
|
||||
// center and rotate to starting position
|
||||
ctx.translate(centerLeft, centerTop);
|
||||
ctx.scale(1, options.series.pie.tilt);
|
||||
|
||||
//radius -= edge;
|
||||
for (var i = 1; i <= edge; i++) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
|
||||
ctx.fill();
|
||||
radius -= i;
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawPie() {
|
||||
var startAngle = Math.PI * options.series.pie.startAngle;
|
||||
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
|
||||
var i;
|
||||
// center and rotate to starting position
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(centerLeft, centerTop);
|
||||
ctx.scale(1, options.series.pie.tilt);
|
||||
//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
|
||||
|
||||
// draw slices
|
||||
ctx.save();
|
||||
|
||||
var currentAngle = startAngle;
|
||||
for (i = 0; i < slices.length; ++i) {
|
||||
slices[i].startAngle = currentAngle;
|
||||
drawSlice(slices[i].angle, slices[i].color, true);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
// draw slice outlines
|
||||
if (options.series.pie.stroke.width > 0) {
|
||||
ctx.save();
|
||||
ctx.lineWidth = options.series.pie.stroke.width;
|
||||
currentAngle = startAngle;
|
||||
for (i = 0; i < slices.length; ++i) {
|
||||
drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// draw donut hole
|
||||
drawDonutHole(ctx);
|
||||
|
||||
ctx.restore();
|
||||
|
||||
// Draw the labels, returning true if they fit within the plot
|
||||
if (options.series.pie.label.show) {
|
||||
return drawLabels();
|
||||
} else return true;
|
||||
|
||||
function drawSlice(angle, color, fill) {
|
||||
if (angle <= 0 || isNaN(angle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fill) {
|
||||
ctx.fillStyle = color;
|
||||
} else {
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineJoin = "round";
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
|
||||
ctx.moveTo(0, 0); // Center of the pie
|
||||
}
|
||||
|
||||
//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
|
||||
ctx.arc(0, 0, radius, currentAngle, currentAngle + angle / 2, false);
|
||||
ctx.arc(0, 0, radius, currentAngle + angle / 2, currentAngle + angle, false);
|
||||
ctx.closePath();
|
||||
//ctx.rotate(angle); // This doesn't work properly in Opera
|
||||
currentAngle += angle;
|
||||
|
||||
if (fill) {
|
||||
ctx.fill();
|
||||
} else {
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function drawLabels() {
|
||||
var currentAngle = startAngle;
|
||||
var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
|
||||
|
||||
for (var i = 0; i < slices.length; ++i) {
|
||||
if (slices[i].percent >= options.series.pie.label.threshold * 100) {
|
||||
if (!drawLabel(slices[i], currentAngle, i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
currentAngle += slices[i].angle;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
function drawLabel(slice, startAngle, index) {
|
||||
if (slice.data[0][1] === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// format label text
|
||||
var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
|
||||
|
||||
if (lf) {
|
||||
text = lf(slice.label, slice);
|
||||
} else {
|
||||
text = slice.label;
|
||||
}
|
||||
|
||||
if (plf) {
|
||||
text = plf(text, slice);
|
||||
}
|
||||
|
||||
var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
|
||||
var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
|
||||
var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
|
||||
|
||||
var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
|
||||
target.append(html);
|
||||
|
||||
var label = target.children("#pieLabel" + index);
|
||||
var labelTop = (y - label.height() / 2);
|
||||
var labelLeft = (x - label.width() / 2);
|
||||
|
||||
label.css("top", labelTop);
|
||||
label.css("left", labelLeft);
|
||||
|
||||
// check to make sure that the label is not outside the canvas
|
||||
if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.series.pie.label.background.opacity !== 0) {
|
||||
// put in the transparent background separately to avoid blended labels and label boxes
|
||||
var c = options.series.pie.label.background.color;
|
||||
if (c == null) {
|
||||
c = slice.color;
|
||||
}
|
||||
|
||||
var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
|
||||
$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
|
||||
.css("opacity", options.series.pie.label.background.opacity)
|
||||
.insertBefore(label);
|
||||
}
|
||||
|
||||
return true;
|
||||
} // end individual label function
|
||||
} // end drawLabels function
|
||||
} // end drawPie function
|
||||
} // end draw function
|
||||
|
||||
// Placed here because it needs to be accessed from multiple locations
|
||||
|
||||
function drawDonutHole(layer) {
|
||||
if (options.series.pie.innerRadius > 0) {
|
||||
// subtract the center
|
||||
layer.save();
|
||||
var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
|
||||
layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
|
||||
layer.beginPath();
|
||||
layer.fillStyle = options.series.pie.stroke.color;
|
||||
layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
|
||||
layer.fill();
|
||||
layer.closePath();
|
||||
layer.restore();
|
||||
|
||||
// add inner stroke
|
||||
layer.save();
|
||||
layer.beginPath();
|
||||
layer.strokeStyle = options.series.pie.stroke.color;
|
||||
layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
|
||||
layer.stroke();
|
||||
layer.closePath();
|
||||
layer.restore();
|
||||
|
||||
// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
|
||||
}
|
||||
}
|
||||
|
||||
//-- Additional Interactive related functions --
|
||||
|
||||
function isPointInPoly(poly, pt) {
|
||||
for (var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) {
|
||||
((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) ||
|
||||
(poly[j][1] <= pt[1] && pt[1] < poly[i][1])) &&
|
||||
(pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) &&
|
||||
(c = !c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
function findNearbySlice(mouseX, mouseY) {
|
||||
var slices = plot.getData(),
|
||||
options = plot.getOptions(),
|
||||
radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
|
||||
x, y;
|
||||
|
||||
for (var i = 0; i < slices.length; ++i) {
|
||||
var s = slices[i];
|
||||
if (s.pie.show) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0); // Center of the pie
|
||||
//ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here.
|
||||
ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
|
||||
ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
|
||||
ctx.closePath();
|
||||
x = mouseX - centerLeft;
|
||||
y = mouseY - centerTop;
|
||||
|
||||
if (ctx.isPointInPath) {
|
||||
if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
|
||||
ctx.restore();
|
||||
return {
|
||||
datapoint: [s.percent, s.data],
|
||||
dataIndex: 0,
|
||||
series: s,
|
||||
seriesIndex: i
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// excanvas for IE doesn;t support isPointInPath, this is a workaround.
|
||||
var p1X = radius * Math.cos(s.startAngle),
|
||||
p1Y = radius * Math.sin(s.startAngle),
|
||||
p2X = radius * Math.cos(s.startAngle + s.angle / 4),
|
||||
p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
|
||||
p3X = radius * Math.cos(s.startAngle + s.angle / 2),
|
||||
p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
|
||||
p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
|
||||
p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
|
||||
p5X = radius * Math.cos(s.startAngle + s.angle),
|
||||
p5Y = radius * Math.sin(s.startAngle + s.angle),
|
||||
arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
|
||||
arrPoint = [x, y];
|
||||
|
||||
// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
|
||||
|
||||
if (isPointInPoly(arrPoly, arrPoint)) {
|
||||
ctx.restore();
|
||||
return {
|
||||
datapoint: [s.percent, s.data],
|
||||
dataIndex: 0,
|
||||
series: s,
|
||||
seriesIndex: i
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function onMouseMove(e) {
|
||||
triggerClickHoverEvent("plothover", e);
|
||||
}
|
||||
|
||||
function onClick(e) {
|
||||
triggerClickHoverEvent("plotclick", e);
|
||||
}
|
||||
|
||||
// trigger click or hover event (they send the same parameters so we share their code)
|
||||
|
||||
function triggerClickHoverEvent(eventname, e) {
|
||||
var offset = plot.offset();
|
||||
var canvasX = parseInt(e.pageX - offset.left);
|
||||
var canvasY = parseInt(e.pageY - offset.top);
|
||||
var item = findNearbySlice(canvasX, canvasY);
|
||||
|
||||
if (options.grid.autoHighlight) {
|
||||
// clear auto-highlights
|
||||
for (var i = 0; i < highlights.length; ++i) {
|
||||
var h = highlights[i];
|
||||
if (h.auto === eventname && !(item && h.series === item.series)) {
|
||||
unhighlight(h.series);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// highlight the slice
|
||||
|
||||
if (item) {
|
||||
highlight(item.series, eventname);
|
||||
}
|
||||
|
||||
// trigger any hover bind events
|
||||
|
||||
var pos = { pageX: e.pageX, pageY: e.pageY };
|
||||
target.trigger(eventname, [pos, item]);
|
||||
}
|
||||
|
||||
function highlight(s, auto) {
|
||||
//if (typeof s == "number") {
|
||||
// s = series[s];
|
||||
//}
|
||||
|
||||
var i = indexOfHighlight(s);
|
||||
|
||||
if (i === -1) {
|
||||
highlights.push({ series: s, auto: auto });
|
||||
plot.triggerRedrawOverlay();
|
||||
} else if (!auto) {
|
||||
highlights[i].auto = false;
|
||||
}
|
||||
}
|
||||
|
||||
function unhighlight(s) {
|
||||
if (s == null) {
|
||||
highlights = [];
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
|
||||
//if (typeof s == "number") {
|
||||
// s = series[s];
|
||||
//}
|
||||
|
||||
var i = indexOfHighlight(s);
|
||||
|
||||
if (i !== -1) {
|
||||
highlights.splice(i, 1);
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
function indexOfHighlight(s) {
|
||||
for (var i = 0; i < highlights.length; ++i) {
|
||||
var h = highlights[i];
|
||||
if (h.series === s) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function drawOverlay(plot, octx) {
|
||||
var options = plot.getOptions();
|
||||
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
|
||||
|
||||
octx.save();
|
||||
octx.translate(centerLeft, centerTop);
|
||||
octx.scale(1, options.series.pie.tilt);
|
||||
|
||||
for (var i = 0; i < highlights.length; ++i) {
|
||||
drawHighlight(highlights[i].series);
|
||||
}
|
||||
|
||||
drawDonutHole(octx);
|
||||
|
||||
octx.restore();
|
||||
|
||||
function drawHighlight(series) {
|
||||
if (series.angle <= 0 || isNaN(series.angle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
|
||||
octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
|
||||
octx.beginPath();
|
||||
if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
|
||||
octx.moveTo(0, 0); // Center of the pie
|
||||
}
|
||||
octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
|
||||
octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
|
||||
octx.closePath();
|
||||
octx.fill();
|
||||
}
|
||||
}
|
||||
} // end init (plugin body)
|
||||
|
||||
// define pie specific options and their default values
|
||||
var options = {
|
||||
series: {
|
||||
pie: {
|
||||
show: false,
|
||||
radius: "auto", // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
|
||||
innerRadius: 0, /* for donut */
|
||||
startAngle: 3 / 2,
|
||||
tilt: 1,
|
||||
shadow: {
|
||||
left: 5, // shadow left offset
|
||||
top: 15, // shadow top offset
|
||||
alpha: 0.02 // shadow alpha
|
||||
},
|
||||
offset: {
|
||||
top: 0,
|
||||
left: "auto"
|
||||
},
|
||||
stroke: {
|
||||
color: "#fff",
|
||||
width: 1
|
||||
},
|
||||
label: {
|
||||
show: "auto",
|
||||
formatter: function(label, slice) {
|
||||
return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
|
||||
}, // formatter function
|
||||
radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
|
||||
background: {
|
||||
color: null,
|
||||
opacity: 0
|
||||
},
|
||||
threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow)
|
||||
},
|
||||
combine: {
|
||||
threshold: -1, // percentage at which to combine little slices into one larger slice
|
||||
color: null, // color to give the new slice (auto-generated if null)
|
||||
label: "Other" // label to give the new slice
|
||||
},
|
||||
highlight: {
|
||||
//color: "#fff", // will add this functionality once parseColor is available
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: "pie",
|
||||
version: "1.1"
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,60 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/* Flot plugin for automatically redrawing plots as the placeholder resizes.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
It works by listening for changes on the placeholder div (through the jQuery
|
||||
resize event plugin) - if the size changes, it will redraw the plot.
|
||||
|
||||
There are no options. If you need to disable the plugin for some plots, you
|
||||
can just fix the size of their placeholders.
|
||||
|
||||
*/
|
||||
|
||||
/* Inline dependency:
|
||||
* jQuery resize event - v1.1 - 3/14/2010
|
||||
* http://benalman.com/projects/jquery-resize-plugin/
|
||||
*
|
||||
* Copyright (c) 2010 "Cowboy" Ben Alman
|
||||
* Dual licensed under the MIT and GPL licenses.
|
||||
* http://benalman.com/about/license/
|
||||
*/
|
||||
(function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);
|
||||
|
||||
/* eslint-enable */
|
||||
(function ($) {
|
||||
var options = { }; // no options
|
||||
|
||||
function init(plot) {
|
||||
function onResize() {
|
||||
var placeholder = plot.getPlaceholder();
|
||||
|
||||
// somebody might have hidden us and we can't plot
|
||||
// when we don't have the dimensions
|
||||
if (placeholder.width() === 0 || placeholder.height() === 0) return;
|
||||
|
||||
plot.resize();
|
||||
plot.setupGrid();
|
||||
plot.draw();
|
||||
}
|
||||
|
||||
function bindEvents(plot, eventHolder) {
|
||||
plot.getPlaceholder().resize(onResize);
|
||||
}
|
||||
|
||||
function shutdown(plot, eventHolder) {
|
||||
plot.getPlaceholder().unbind("resize", onResize);
|
||||
}
|
||||
|
||||
plot.hooks.bindEvents.push(bindEvents);
|
||||
plot.hooks.shutdown.push(shutdown);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'resize',
|
||||
version: '1.0'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,43 +0,0 @@
|
||||
(function ($) {
|
||||
'use strict';
|
||||
var saturated = {
|
||||
saturate: function (a) {
|
||||
if (a === Infinity) {
|
||||
return Number.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (a === -Infinity) {
|
||||
return -Number.MAX_VALUE;
|
||||
}
|
||||
|
||||
return a;
|
||||
},
|
||||
delta: function(min, max, noTicks) {
|
||||
return ((max - min) / noTicks) === Infinity ? (max / noTicks - min / noTicks) : (max - min) / noTicks
|
||||
},
|
||||
multiply: function (a, b) {
|
||||
return saturated.saturate(a * b);
|
||||
},
|
||||
// returns c * bInt * a. Beahves properly in the case where c is negative
|
||||
// and bInt * a is bigger that Number.MAX_VALUE (Infinity)
|
||||
multiplyAdd: function (a, bInt, c) {
|
||||
if (isFinite(a * bInt)) {
|
||||
return saturated.saturate(a * bInt + c);
|
||||
} else {
|
||||
var result = c;
|
||||
|
||||
for (var i = 0; i < bInt; i++) {
|
||||
result += a;
|
||||
}
|
||||
|
||||
return saturated.saturate(result);
|
||||
}
|
||||
},
|
||||
// round to nearby lower multiple of base
|
||||
floorInBase: function(n, base) {
|
||||
return base * Math.floor(n / base);
|
||||
}
|
||||
};
|
||||
|
||||
$.plot.saturated = saturated;
|
||||
})(jQuery);
|
||||
@@ -1,517 +0,0 @@
|
||||
/* Flot plugin for selecting regions of a plot.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The plugin supports these options:
|
||||
|
||||
selection: {
|
||||
mode: null or "x" or "y" or "xy" or "smart",
|
||||
color: color,
|
||||
shape: "round" or "miter" or "bevel",
|
||||
visualization: "fill" or "focus",
|
||||
minSize: number of pixels
|
||||
}
|
||||
|
||||
Selection support is enabled by setting the mode to one of "x", "y" or "xy".
|
||||
In "x" mode, the user will only be able to specify the x range, similarly for
|
||||
"y" mode. For "xy", the selection becomes a rectangle where both ranges can be
|
||||
specified. "color" is color of the selection (if you need to change the color
|
||||
later on, you can get to it with plot.getOptions().selection.color). "shape"
|
||||
is the shape of the corners of the selection.
|
||||
|
||||
The way how the selection is visualized, can be changed by using the option
|
||||
"visualization". Flot currently supports two modes: "focus" and "fill". The
|
||||
option "focus" draws a colored bezel around the selected area while keeping
|
||||
the selected area clear. The option "fill" highlights (i.e., fills) the
|
||||
selected area with a colored highlight.
|
||||
|
||||
"minSize" is the minimum size a selection can be in pixels. This value can
|
||||
be customized to determine the smallest size a selection can be and still
|
||||
have the selection rectangle be displayed. When customizing this value, the
|
||||
fact that it refers to pixels, not axis units must be taken into account.
|
||||
Thus, for example, if there is a bar graph in time mode with BarWidth set to 1
|
||||
minute, setting "minSize" to 1 will not make the minimum selection size 1
|
||||
minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent
|
||||
"plotunselected" events from being fired when the user clicks the mouse without
|
||||
dragging.
|
||||
|
||||
When selection support is enabled, a "plotselected" event will be emitted on
|
||||
the DOM element you passed into the plot function. The event handler gets a
|
||||
parameter with the ranges selected on the axes, like this:
|
||||
|
||||
placeholder.bind( "plotselected", function( event, ranges ) {
|
||||
alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
|
||||
// similar for yaxis - with multiple axes, the extra ones are in
|
||||
// x2axis, x3axis, ...
|
||||
});
|
||||
|
||||
The "plotselected" event is only fired when the user has finished making the
|
||||
selection. A "plotselecting" event is fired during the process with the same
|
||||
parameters as the "plotselected" event, in case you want to know what's
|
||||
happening while it's happening,
|
||||
|
||||
A "plotunselected" event with no arguments is emitted when the user clicks the
|
||||
mouse to remove the selection. As stated above, setting "minSize" to 0 will
|
||||
destroy this behavior.
|
||||
|
||||
The plugin allso adds the following methods to the plot object:
|
||||
|
||||
- setSelection( ranges, preventEvent )
|
||||
|
||||
Set the selection rectangle. The passed in ranges is on the same form as
|
||||
returned in the "plotselected" event. If the selection mode is "x", you
|
||||
should put in either an xaxis range, if the mode is "y" you need to put in
|
||||
an yaxis range and both xaxis and yaxis if the selection mode is "xy", like
|
||||
this:
|
||||
|
||||
setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
|
||||
|
||||
setSelection will trigger the "plotselected" event when called. If you don't
|
||||
want that to happen, e.g. if you're inside a "plotselected" handler, pass
|
||||
true as the second parameter. If you are using multiple axes, you can
|
||||
specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of
|
||||
xaxis, the plugin picks the first one it sees.
|
||||
|
||||
- clearSelection( preventEvent )
|
||||
|
||||
Clear the selection rectangle. Pass in true to avoid getting a
|
||||
"plotunselected" event.
|
||||
|
||||
- getSelection()
|
||||
|
||||
Returns the current selection in the same format as the "plotselected"
|
||||
event. If there's currently no selection, the function returns null.
|
||||
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
function init(plot) {
|
||||
var selection = {
|
||||
first: {x: -1, y: -1},
|
||||
second: {x: -1, y: -1},
|
||||
show: false,
|
||||
currentMode: 'xy',
|
||||
active: false
|
||||
};
|
||||
|
||||
var SNAPPING_CONSTANT = $.plot.uiConstants.SNAPPING_CONSTANT;
|
||||
|
||||
// FIXME: The drag handling implemented here should be
|
||||
// abstracted out, there's some similar code from a library in
|
||||
// the navigation plugin, this should be massaged a bit to fit
|
||||
// the Flot cases here better and reused. Doing this would
|
||||
// make this plugin much slimmer.
|
||||
var savedhandlers = {};
|
||||
|
||||
function onDrag(e) {
|
||||
if (selection.active) {
|
||||
updateSelection(e);
|
||||
|
||||
plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
|
||||
}
|
||||
}
|
||||
|
||||
function onDragStart(e) {
|
||||
var o = plot.getOptions();
|
||||
// only accept left-click
|
||||
if (e.which !== 1 || o.selection.mode === null) return;
|
||||
|
||||
// reinitialize currentMode
|
||||
selection.currentMode = 'xy';
|
||||
|
||||
// cancel out any text selections
|
||||
document.body.focus();
|
||||
|
||||
// prevent text selection and drag in old-school browsers
|
||||
if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
|
||||
savedhandlers.onselectstart = document.onselectstart;
|
||||
document.onselectstart = function () { return false; };
|
||||
}
|
||||
if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
|
||||
savedhandlers.ondrag = document.ondrag;
|
||||
document.ondrag = function () { return false; };
|
||||
}
|
||||
|
||||
setSelectionPos(selection.first, e);
|
||||
|
||||
selection.active = true;
|
||||
}
|
||||
|
||||
function onDragEnd(e) {
|
||||
// revert drag stuff for old-school browsers
|
||||
if (document.onselectstart !== undefined) {
|
||||
document.onselectstart = savedhandlers.onselectstart;
|
||||
}
|
||||
|
||||
if (document.ondrag !== undefined) {
|
||||
document.ondrag = savedhandlers.ondrag;
|
||||
}
|
||||
|
||||
// no more dragging
|
||||
selection.active = false;
|
||||
updateSelection(e);
|
||||
|
||||
if (selectionIsSane()) {
|
||||
triggerSelectedEvent();
|
||||
} else {
|
||||
// this counts as a clear
|
||||
plot.getPlaceholder().trigger("plotunselected", [ ]);
|
||||
plot.getPlaceholder().trigger("plotselecting", [ null ]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getSelection() {
|
||||
if (!selectionIsSane()) return null;
|
||||
|
||||
if (!selection.show) return null;
|
||||
|
||||
var r = {},
|
||||
c1 = {x: selection.first.x, y: selection.first.y},
|
||||
c2 = {x: selection.second.x, y: selection.second.y};
|
||||
|
||||
if (selectionDirection(plot) === 'x') {
|
||||
c1.y = 0;
|
||||
c2.y = plot.height();
|
||||
}
|
||||
|
||||
if (selectionDirection(plot) === 'y') {
|
||||
c1.x = 0;
|
||||
c2.x = plot.width();
|
||||
}
|
||||
|
||||
$.each(plot.getAxes(), function (name, axis) {
|
||||
if (axis.used) {
|
||||
var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
|
||||
r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
|
||||
}
|
||||
});
|
||||
return r;
|
||||
}
|
||||
|
||||
function triggerSelectedEvent() {
|
||||
var r = getSelection();
|
||||
|
||||
plot.getPlaceholder().trigger("plotselected", [ r ]);
|
||||
|
||||
// backwards-compat stuff, to be removed in future
|
||||
if (r.xaxis && r.yaxis) {
|
||||
plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
|
||||
}
|
||||
}
|
||||
|
||||
function clamp(min, value, max) {
|
||||
return value < min ? min : (value > max ? max : value);
|
||||
}
|
||||
|
||||
function selectionDirection(plot) {
|
||||
var o = plot.getOptions();
|
||||
|
||||
if (o.selection.mode === 'smart') {
|
||||
return selection.currentMode;
|
||||
} else {
|
||||
return o.selection.mode;
|
||||
}
|
||||
}
|
||||
|
||||
function updateMode(pos) {
|
||||
if (selection.first) {
|
||||
var delta = {
|
||||
x: pos.x - selection.first.x,
|
||||
y: pos.y - selection.first.y
|
||||
};
|
||||
|
||||
if (Math.abs(delta.x) < SNAPPING_CONSTANT) {
|
||||
selection.currentMode = 'y';
|
||||
} else if (Math.abs(delta.y) < SNAPPING_CONSTANT) {
|
||||
selection.currentMode = 'x';
|
||||
} else {
|
||||
selection.currentMode = 'xy';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectionPos(pos, e) {
|
||||
var offset = plot.getPlaceholder().offset();
|
||||
var plotOffset = plot.getPlotOffset();
|
||||
pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
|
||||
pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
|
||||
|
||||
if (pos !== selection.first) updateMode(pos);
|
||||
|
||||
if (selectionDirection(plot) === "y") {
|
||||
pos.x = pos === selection.first ? 0 : plot.width();
|
||||
}
|
||||
|
||||
if (selectionDirection(plot) === "x") {
|
||||
pos.y = pos === selection.first ? 0 : plot.height();
|
||||
}
|
||||
}
|
||||
|
||||
function updateSelection(pos) {
|
||||
if (pos.pageX == null) return;
|
||||
|
||||
setSelectionPos(selection.second, pos);
|
||||
if (selectionIsSane()) {
|
||||
selection.show = true;
|
||||
plot.triggerRedrawOverlay();
|
||||
} else clearSelection(true);
|
||||
}
|
||||
|
||||
function clearSelection(preventEvent) {
|
||||
if (selection.show) {
|
||||
selection.show = false;
|
||||
selection.currentMode = '';
|
||||
plot.triggerRedrawOverlay();
|
||||
if (!preventEvent) {
|
||||
plot.getPlaceholder().trigger("plotunselected", [ ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// function taken from markings support in Flot
|
||||
function extractRange(ranges, coord) {
|
||||
var axis, from, to, key, axes = plot.getAxes();
|
||||
|
||||
for (var k in axes) {
|
||||
axis = axes[k];
|
||||
if (axis.direction === coord) {
|
||||
key = coord + axis.n + "axis";
|
||||
if (!ranges[key] && axis.n === 1) {
|
||||
// support x1axis as xaxis
|
||||
key = coord + "axis";
|
||||
}
|
||||
|
||||
if (ranges[key]) {
|
||||
from = ranges[key].from;
|
||||
to = ranges[key].to;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// backwards-compat stuff - to be removed in future
|
||||
if (!ranges[key]) {
|
||||
axis = coord === "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
|
||||
from = ranges[coord + "1"];
|
||||
to = ranges[coord + "2"];
|
||||
}
|
||||
|
||||
// auto-reverse as an added bonus
|
||||
if (from != null && to != null && from > to) {
|
||||
var tmp = from;
|
||||
from = to;
|
||||
to = tmp;
|
||||
}
|
||||
|
||||
return { from: from, to: to, axis: axis };
|
||||
}
|
||||
|
||||
function setSelection(ranges, preventEvent) {
|
||||
var range;
|
||||
|
||||
if (selectionDirection(plot) === "y") {
|
||||
selection.first.x = 0;
|
||||
selection.second.x = plot.width();
|
||||
} else {
|
||||
range = extractRange(ranges, "x");
|
||||
selection.first.x = range.axis.p2c(range.from);
|
||||
selection.second.x = range.axis.p2c(range.to);
|
||||
}
|
||||
|
||||
if (selectionDirection(plot) === "x") {
|
||||
selection.first.y = 0;
|
||||
selection.second.y = plot.height();
|
||||
} else {
|
||||
range = extractRange(ranges, "y");
|
||||
selection.first.y = range.axis.p2c(range.from);
|
||||
selection.second.y = range.axis.p2c(range.to);
|
||||
}
|
||||
|
||||
selection.show = true;
|
||||
plot.triggerRedrawOverlay();
|
||||
if (!preventEvent && selectionIsSane()) {
|
||||
triggerSelectedEvent();
|
||||
}
|
||||
}
|
||||
|
||||
function selectionIsSane() {
|
||||
var minSize = plot.getOptions().selection.minSize;
|
||||
return Math.abs(selection.second.x - selection.first.x) >= minSize &&
|
||||
Math.abs(selection.second.y - selection.first.y) >= minSize;
|
||||
}
|
||||
|
||||
plot.clearSelection = clearSelection;
|
||||
plot.setSelection = setSelection;
|
||||
plot.getSelection = getSelection;
|
||||
|
||||
plot.hooks.bindEvents.push(function(plot, eventHolder) {
|
||||
var o = plot.getOptions();
|
||||
if (o.selection.mode != null) {
|
||||
plot.addEventHandler("dragstart", onDragStart, eventHolder, 0);
|
||||
plot.addEventHandler("drag", onDrag, eventHolder, 0);
|
||||
plot.addEventHandler("dragend", onDragEnd, eventHolder, 0);
|
||||
}
|
||||
});
|
||||
|
||||
function drawSelectionDecorations(ctx, x, y, w, h, oX, oY, mode) {
|
||||
var spacing = 3;
|
||||
var fullEarWidth = 15;
|
||||
var earWidth = Math.max(0, Math.min(fullEarWidth, w / 2 - 2, h / 2 - 2));
|
||||
ctx.fillStyle = '#ffffff';
|
||||
|
||||
if (mode === 'xy') {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y + earWidth);
|
||||
ctx.lineTo(x - 3, y + earWidth);
|
||||
ctx.lineTo(x - 3, y - 3);
|
||||
ctx.lineTo(x + earWidth, y - 3);
|
||||
ctx.lineTo(x + earWidth, y);
|
||||
ctx.lineTo(x, y);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.moveTo(x, y + h - earWidth);
|
||||
ctx.lineTo(x - 3, y + h - earWidth);
|
||||
ctx.lineTo(x - 3, y + h + 3);
|
||||
ctx.lineTo(x + earWidth, y + h + 3);
|
||||
ctx.lineTo(x + earWidth, y + h);
|
||||
ctx.lineTo(x, y + h);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.moveTo(x + w, y + earWidth);
|
||||
ctx.lineTo(x + w + 3, y + earWidth);
|
||||
ctx.lineTo(x + w + 3, y - 3);
|
||||
ctx.lineTo(x + w - earWidth, y - 3);
|
||||
ctx.lineTo(x + w - earWidth, y);
|
||||
ctx.lineTo(x + w, y);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.moveTo(x + w, y + h - earWidth);
|
||||
ctx.lineTo(x + w + 3, y + h - earWidth);
|
||||
ctx.lineTo(x + w + 3, y + h + 3);
|
||||
ctx.lineTo(x + w - earWidth, y + h + 3);
|
||||
ctx.lineTo(x + w - earWidth, y + h);
|
||||
ctx.lineTo(x + w, y + h);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
x = oX;
|
||||
y = oY;
|
||||
|
||||
if (mode === 'x') {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y + fullEarWidth);
|
||||
ctx.lineTo(x, y - fullEarWidth);
|
||||
ctx.lineTo(x - spacing, y - fullEarWidth);
|
||||
ctx.lineTo(x - spacing, y + fullEarWidth);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.moveTo(x + w, y + fullEarWidth);
|
||||
ctx.lineTo(x + w, y - fullEarWidth);
|
||||
ctx.lineTo(x + w + spacing, y - fullEarWidth);
|
||||
ctx.lineTo(x + w + spacing, y + fullEarWidth);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
if (mode === 'y') {
|
||||
ctx.beginPath();
|
||||
|
||||
ctx.moveTo(x - fullEarWidth, y);
|
||||
ctx.lineTo(x + fullEarWidth, y);
|
||||
ctx.lineTo(x + fullEarWidth, y - spacing);
|
||||
ctx.lineTo(x - fullEarWidth, y - spacing);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.moveTo(x - fullEarWidth, y + h);
|
||||
ctx.lineTo(x + fullEarWidth, y + h);
|
||||
ctx.lineTo(x + fullEarWidth, y + h + spacing);
|
||||
ctx.lineTo(x - fullEarWidth, y + h + spacing);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
plot.hooks.drawOverlay.push(function (plot, ctx) {
|
||||
// draw selection
|
||||
if (selection.show && selectionIsSane()) {
|
||||
var plotOffset = plot.getPlotOffset();
|
||||
var o = plot.getOptions();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(plotOffset.left, plotOffset.top);
|
||||
|
||||
var c = $.color.parse(o.selection.color);
|
||||
var visualization = o.selection.visualization;
|
||||
|
||||
var scalingFactor = 1;
|
||||
|
||||
// use a dimmer scaling factor if visualization is "fill"
|
||||
if (visualization === "fill") {
|
||||
scalingFactor = 0.8;
|
||||
}
|
||||
|
||||
ctx.strokeStyle = c.scale('a', scalingFactor).toString();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.lineJoin = o.selection.shape;
|
||||
ctx.fillStyle = c.scale('a', 0.4).toString();
|
||||
|
||||
var x = Math.min(selection.first.x, selection.second.x) + 0.5,
|
||||
oX = x,
|
||||
y = Math.min(selection.first.y, selection.second.y) + 0.5,
|
||||
oY = y,
|
||||
w = Math.abs(selection.second.x - selection.first.x) - 1,
|
||||
h = Math.abs(selection.second.y - selection.first.y) - 1;
|
||||
|
||||
if (selectionDirection(plot) === 'x') {
|
||||
h += y;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
if (selectionDirection(plot) === 'y') {
|
||||
w += x;
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (visualization === "fill") {
|
||||
ctx.fillRect(x, y, w, h);
|
||||
ctx.strokeRect(x, y, w, h);
|
||||
} else {
|
||||
ctx.fillRect(0, 0, plot.width(), plot.height());
|
||||
ctx.clearRect(x, y, w, h);
|
||||
drawSelectionDecorations(ctx, x, y, w, h, oX, oY, selectionDirection(plot));
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
});
|
||||
|
||||
plot.hooks.shutdown.push(function (plot, eventHolder) {
|
||||
eventHolder.unbind("dragstart", onDragStart);
|
||||
eventHolder.unbind("drag", onDrag);
|
||||
eventHolder.unbind("dragend", onDragEnd);
|
||||
});
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: {
|
||||
selection: {
|
||||
mode: null, // one of null, "x", "y" or "xy"
|
||||
visualization: "focus", // "focus" or "fill"
|
||||
color: "#888888",
|
||||
shape: "round", // one of "round", "miter", or "bevel"
|
||||
minSize: 5 // minimum number of pixels
|
||||
}
|
||||
},
|
||||
name: 'selection',
|
||||
version: '1.1'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,220 +0,0 @@
|
||||
/* Flot plugin for stacking data sets rather than overlaying them.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The plugin assumes the data is sorted on x (or y if stacking horizontally).
|
||||
For line charts, it is assumed that if a line has an undefined gap (from a
|
||||
null point), then the line above it should have the same gap - insert zeros
|
||||
instead of "null" if you want another behaviour. This also holds for the start
|
||||
and end of the chart. Note that stacking a mix of positive and negative values
|
||||
in most instances doesn't make sense (so it looks weird).
|
||||
|
||||
Two or more series are stacked when their "stack" attribute is set to the same
|
||||
key (which can be any number or string or just "true"). To specify the default
|
||||
stack, you can set the stack option like this:
|
||||
|
||||
series: {
|
||||
stack: null/false, true, or a key (number/string)
|
||||
}
|
||||
|
||||
You can also specify it for a single series, like this:
|
||||
|
||||
$.plot( $("#placeholder"), [{
|
||||
data: [ ... ],
|
||||
stack: true
|
||||
}])
|
||||
|
||||
The stacking order is determined by the order of the data series in the array
|
||||
(later series end up on top of the previous).
|
||||
|
||||
Internally, the plugin modifies the datapoints in each series, adding an
|
||||
offset to the y value. For line series, extra data points are inserted through
|
||||
interpolation. If there's a second y value, it's also adjusted (e.g for bar
|
||||
charts or filled areas).
|
||||
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var options = {
|
||||
series: { stack: null } // or number/string
|
||||
};
|
||||
|
||||
function init(plot) {
|
||||
function findMatchingSeries(s, allseries) {
|
||||
var res = null;
|
||||
for (var i = 0; i < allseries.length; ++i) {
|
||||
if (s === allseries[i]) break;
|
||||
|
||||
if (allseries[i].stack === s.stack) {
|
||||
res = allseries[i];
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function addBottomPoints (s, datapoints) {
|
||||
var formattedPoints = [];
|
||||
for (var i = 0; i < datapoints.points.length; i += 2) {
|
||||
formattedPoints.push(datapoints.points[i]);
|
||||
formattedPoints.push(datapoints.points[i + 1]);
|
||||
formattedPoints.push(0);
|
||||
}
|
||||
|
||||
datapoints.format.push({
|
||||
x: false,
|
||||
y: true,
|
||||
number: true,
|
||||
required: false,
|
||||
computeRange: s.yaxis.options.autoScale !== 'none',
|
||||
defaultValue: 0
|
||||
});
|
||||
datapoints.points = formattedPoints;
|
||||
datapoints.pointsize = 3;
|
||||
}
|
||||
|
||||
function stackData(plot, s, datapoints) {
|
||||
if (s.stack == null || s.stack === false) return;
|
||||
|
||||
var needsBottom = s.bars.show || (s.lines.show && s.lines.fill);
|
||||
var hasBottom = datapoints.pointsize > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y);
|
||||
// Series data is missing bottom points - need to format
|
||||
if (needsBottom && !hasBottom) {
|
||||
addBottomPoints(s, datapoints);
|
||||
}
|
||||
|
||||
var other = findMatchingSeries(s, plot.getData());
|
||||
if (!other) return;
|
||||
|
||||
var ps = datapoints.pointsize,
|
||||
points = datapoints.points,
|
||||
otherps = other.datapoints.pointsize,
|
||||
otherpoints = other.datapoints.points,
|
||||
newpoints = [],
|
||||
px, py, intery, qx, qy, bottom,
|
||||
withlines = s.lines.show,
|
||||
horizontal = s.bars.horizontal,
|
||||
withsteps = withlines && s.lines.steps,
|
||||
fromgap = true,
|
||||
keyOffset = horizontal ? 1 : 0,
|
||||
accumulateOffset = horizontal ? 0 : 1,
|
||||
i = 0, j = 0, l, m;
|
||||
|
||||
while (true) {
|
||||
if (i >= points.length) break;
|
||||
|
||||
l = newpoints.length;
|
||||
|
||||
if (points[i] == null) {
|
||||
// copy gaps
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints.push(points[i + m]);
|
||||
}
|
||||
|
||||
i += ps;
|
||||
} else if (j >= otherpoints.length) {
|
||||
// for lines, we can't use the rest of the points
|
||||
if (!withlines) {
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints.push(points[i + m]);
|
||||
}
|
||||
}
|
||||
|
||||
i += ps;
|
||||
} else if (otherpoints[j] == null) {
|
||||
// oops, got a gap
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints.push(null);
|
||||
}
|
||||
|
||||
fromgap = true;
|
||||
j += otherps;
|
||||
} else {
|
||||
// cases where we actually got two points
|
||||
px = points[i + keyOffset];
|
||||
py = points[i + accumulateOffset];
|
||||
qx = otherpoints[j + keyOffset];
|
||||
qy = otherpoints[j + accumulateOffset];
|
||||
bottom = 0;
|
||||
|
||||
if (px === qx) {
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints.push(points[i + m]);
|
||||
}
|
||||
|
||||
newpoints[l + accumulateOffset] += qy;
|
||||
bottom = qy;
|
||||
|
||||
i += ps;
|
||||
j += otherps;
|
||||
} else if (px > qx) {
|
||||
// we got past point below, might need to
|
||||
// insert interpolated extra point
|
||||
if (withlines && i > 0 && points[i - ps] != null) {
|
||||
intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
|
||||
newpoints.push(qx);
|
||||
newpoints.push(intery + qy);
|
||||
for (m = 2; m < ps; ++m) {
|
||||
newpoints.push(points[i + m]);
|
||||
}
|
||||
|
||||
bottom = qy;
|
||||
}
|
||||
|
||||
j += otherps;
|
||||
} else { // px < qx
|
||||
if (fromgap && withlines) {
|
||||
// if we come from a gap, we just skip this point
|
||||
i += ps;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints.push(points[i + m]);
|
||||
}
|
||||
|
||||
// we might be able to interpolate a point below,
|
||||
// this can give us a better y
|
||||
if (withlines && j > 0 && otherpoints[j - otherps] != null) {
|
||||
bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
|
||||
}
|
||||
|
||||
newpoints[l + accumulateOffset] += bottom;
|
||||
|
||||
i += ps;
|
||||
}
|
||||
|
||||
fromgap = false;
|
||||
|
||||
if (l !== newpoints.length && needsBottom) {
|
||||
newpoints[l + 2] += bottom;
|
||||
}
|
||||
}
|
||||
|
||||
// maintain the line steps invariant
|
||||
if (withsteps && l !== newpoints.length && l > 0 &&
|
||||
newpoints[l] !== null &&
|
||||
newpoints[l] !== newpoints[l - ps] &&
|
||||
newpoints[l + 1] !== newpoints[l - ps + 1]) {
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints[l + ps + m] = newpoints[l + m];
|
||||
}
|
||||
|
||||
newpoints[l + 1] = newpoints[l - ps + 1];
|
||||
}
|
||||
}
|
||||
|
||||
datapoints.points = newpoints;
|
||||
}
|
||||
|
||||
plot.hooks.processDatapoints.push(stackData);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'stack',
|
||||
version: '1.2'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,98 +0,0 @@
|
||||
/* Flot plugin that adds some extra symbols for plotting points.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The symbols are accessed as strings through the standard symbol options:
|
||||
|
||||
series: {
|
||||
points: {
|
||||
symbol: "square" // or "diamond", "triangle", "cross", "plus", "ellipse", "rectangle"
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
// we normalize the area of each symbol so it is approximately the
|
||||
// same as a circle of the given radius
|
||||
|
||||
var square = function (ctx, x, y, radius, shadow) {
|
||||
// pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2
|
||||
var size = radius * Math.sqrt(Math.PI) / 2;
|
||||
ctx.rect(x - size, y - size, size + size, size + size);
|
||||
},
|
||||
rectangle = function (ctx, x, y, radius, shadow) {
|
||||
// pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2
|
||||
var size = radius * Math.sqrt(Math.PI) / 2;
|
||||
ctx.rect(x - size, y - size, size + size, size + size);
|
||||
},
|
||||
diamond = function (ctx, x, y, radius, shadow) {
|
||||
// pi * r^2 = 2s^2 => s = r * sqrt(pi/2)
|
||||
var size = radius * Math.sqrt(Math.PI / 2);
|
||||
ctx.moveTo(x - size, y);
|
||||
ctx.lineTo(x, y - size);
|
||||
ctx.lineTo(x + size, y);
|
||||
ctx.lineTo(x, y + size);
|
||||
ctx.lineTo(x - size, y);
|
||||
ctx.lineTo(x, y - size);
|
||||
},
|
||||
triangle = function (ctx, x, y, radius, shadow) {
|
||||
// pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3))
|
||||
var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3));
|
||||
var height = size * Math.sin(Math.PI / 3);
|
||||
ctx.moveTo(x - size / 2, y + height / 2);
|
||||
ctx.lineTo(x + size / 2, y + height / 2);
|
||||
if (!shadow) {
|
||||
ctx.lineTo(x, y - height / 2);
|
||||
ctx.lineTo(x - size / 2, y + height / 2);
|
||||
ctx.lineTo(x + size / 2, y + height / 2);
|
||||
}
|
||||
},
|
||||
cross = function (ctx, x, y, radius, shadow) {
|
||||
// pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2
|
||||
var size = radius * Math.sqrt(Math.PI) / 2;
|
||||
ctx.moveTo(x - size, y - size);
|
||||
ctx.lineTo(x + size, y + size);
|
||||
ctx.moveTo(x - size, y + size);
|
||||
ctx.lineTo(x + size, y - size);
|
||||
},
|
||||
ellipse = function(ctx, x, y, radius, shadow, fill) {
|
||||
if (!shadow) {
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
|
||||
}
|
||||
},
|
||||
plus = function (ctx, x, y, radius, shadow) {
|
||||
var size = radius * Math.sqrt(Math.PI / 2);
|
||||
ctx.moveTo(x - size, y);
|
||||
ctx.lineTo(x + size, y);
|
||||
ctx.moveTo(x, y + size);
|
||||
ctx.lineTo(x, y - size);
|
||||
},
|
||||
handlers = {
|
||||
square: square,
|
||||
rectangle: rectangle,
|
||||
diamond: diamond,
|
||||
triangle: triangle,
|
||||
cross: cross,
|
||||
ellipse: ellipse,
|
||||
plus: plus
|
||||
};
|
||||
|
||||
square.fill = true;
|
||||
rectangle.fill = true;
|
||||
diamond.fill = true;
|
||||
triangle.fill = true;
|
||||
ellipse.fill = true;
|
||||
|
||||
function init(plot) {
|
||||
plot.drawSymbol = handlers;
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
name: 'symbols',
|
||||
version: '1.0'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,143 +0,0 @@
|
||||
/* Flot plugin for thresholding data.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The plugin supports these options:
|
||||
|
||||
series: {
|
||||
threshold: {
|
||||
below: number
|
||||
color: colorspec
|
||||
}
|
||||
}
|
||||
|
||||
It can also be applied to a single series, like this:
|
||||
|
||||
$.plot( $("#placeholder"), [{
|
||||
data: [ ... ],
|
||||
threshold: { ... }
|
||||
}])
|
||||
|
||||
An array can be passed for multiple thresholding, like this:
|
||||
|
||||
threshold: [{
|
||||
below: number1
|
||||
color: color1
|
||||
},{
|
||||
below: number2
|
||||
color: color2
|
||||
}]
|
||||
|
||||
These multiple threshold objects can be passed in any order since they are
|
||||
sorted by the processing function.
|
||||
|
||||
The data points below "below" are drawn with the specified color. This makes
|
||||
it easy to mark points below 0, e.g. for budget data.
|
||||
|
||||
Internally, the plugin works by splitting the data into two series, above and
|
||||
below the threshold. The extra series below the threshold will have its label
|
||||
cleared and the special "originSeries" attribute set to the original series.
|
||||
You may need to check for this in hover events.
|
||||
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var options = {
|
||||
series: { threshold: null } // or { below: number, color: color spec}
|
||||
};
|
||||
|
||||
function init(plot) {
|
||||
function thresholdData(plot, s, datapoints, below, color) {
|
||||
var ps = datapoints.pointsize, i, x, y, p, prevp,
|
||||
thresholded = $.extend({}, s); // note: shallow copy
|
||||
|
||||
thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format };
|
||||
thresholded.label = null;
|
||||
thresholded.color = color;
|
||||
thresholded.threshold = null;
|
||||
thresholded.originSeries = s;
|
||||
thresholded.data = [];
|
||||
|
||||
var origpoints = datapoints.points,
|
||||
addCrossingPoints = s.lines.show;
|
||||
|
||||
var threspoints = [];
|
||||
var newpoints = [];
|
||||
var m;
|
||||
|
||||
for (i = 0; i < origpoints.length; i += ps) {
|
||||
x = origpoints[i];
|
||||
y = origpoints[i + 1];
|
||||
|
||||
prevp = p;
|
||||
if (y < below) p = threspoints;
|
||||
else p = newpoints;
|
||||
|
||||
if (addCrossingPoints && prevp !== p &&
|
||||
x !== null && i > 0 &&
|
||||
origpoints[i - ps] != null) {
|
||||
var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]);
|
||||
prevp.push(interx);
|
||||
prevp.push(below);
|
||||
for (m = 2; m < ps; ++m) {
|
||||
prevp.push(origpoints[i + m]);
|
||||
}
|
||||
|
||||
p.push(null); // start new segment
|
||||
p.push(null);
|
||||
for (m = 2; m < ps; ++m) {
|
||||
p.push(origpoints[i + m]);
|
||||
}
|
||||
|
||||
p.push(interx);
|
||||
p.push(below);
|
||||
for (m = 2; m < ps; ++m) {
|
||||
p.push(origpoints[i + m]);
|
||||
}
|
||||
}
|
||||
|
||||
p.push(x);
|
||||
p.push(y);
|
||||
for (m = 2; m < ps; ++m) {
|
||||
p.push(origpoints[i + m]);
|
||||
}
|
||||
}
|
||||
|
||||
datapoints.points = newpoints;
|
||||
thresholded.datapoints.points = threspoints;
|
||||
|
||||
if (thresholded.datapoints.points.length > 0) {
|
||||
var origIndex = $.inArray(s, plot.getData());
|
||||
// Insert newly-generated series right after original one (to prevent it from becoming top-most)
|
||||
plot.getData().splice(origIndex + 1, 0, thresholded);
|
||||
}
|
||||
|
||||
// FIXME: there are probably some edge cases left in bars
|
||||
}
|
||||
|
||||
function processThresholds(plot, s, datapoints) {
|
||||
if (!s.threshold) return;
|
||||
if (s.threshold instanceof Array) {
|
||||
s.threshold.sort(function(a, b) {
|
||||
return a.below - b.below;
|
||||
});
|
||||
|
||||
$(s.threshold).each(function(i, th) {
|
||||
thresholdData(plot, s, datapoints, th.below, th.color);
|
||||
});
|
||||
} else {
|
||||
thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color);
|
||||
}
|
||||
}
|
||||
|
||||
plot.hooks.processDatapoints.push(processThresholds);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'threshold',
|
||||
version: '1.2'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,585 +0,0 @@
|
||||
/* Pretty handling of time axes.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
Set axis.mode to "time" to enable. See the section "Time series data" in
|
||||
API.txt for details.
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var options = {
|
||||
xaxis: {
|
||||
timezone: null, // "browser" for local to the client or timezone for timezone-js
|
||||
timeformat: null, // format string to use
|
||||
twelveHourClock: false, // 12 or 24 time in time mode
|
||||
monthNames: null, // list of names of months
|
||||
timeBase: 'seconds' // are the values in given in mircoseconds, milliseconds or seconds
|
||||
},
|
||||
yaxis: {
|
||||
timeBase: 'seconds'
|
||||
}
|
||||
};
|
||||
|
||||
var floorInBase = $.plot.saturated.floorInBase;
|
||||
|
||||
// Method to provide microsecond support to Date like classes.
|
||||
var CreateMicroSecondDate = function(dateType, microEpoch) {
|
||||
var newDate = new dateType(microEpoch);
|
||||
|
||||
var oldSetTime = newDate.setTime.bind(newDate);
|
||||
newDate.update = function(microEpoch) {
|
||||
oldSetTime(microEpoch);
|
||||
|
||||
// Round epoch to 3 decimal accuracy
|
||||
microEpoch = Math.round(microEpoch*1000)/1000;
|
||||
|
||||
// Microseconds are stored as integers
|
||||
this.microseconds = 1000 * (microEpoch - Math.floor(microEpoch));
|
||||
};
|
||||
|
||||
var oldGetTime = newDate.getTime.bind(newDate);
|
||||
newDate.getTime = function () {
|
||||
var microEpoch = oldGetTime() + this.microseconds / 1000;
|
||||
return microEpoch;
|
||||
};
|
||||
|
||||
newDate.setTime = function (microEpoch) {
|
||||
this.update(microEpoch);
|
||||
};
|
||||
|
||||
newDate.getMicroseconds = function() {
|
||||
return this.microseconds;
|
||||
};
|
||||
|
||||
newDate.setMicroseconds = function(microseconds) {
|
||||
var epochWithoutMicroseconds = oldGetTime();
|
||||
var newEpoch = epochWithoutMicroseconds + microseconds/1000;
|
||||
this.update(newEpoch);
|
||||
};
|
||||
|
||||
newDate.setUTCMicroseconds = function(microseconds) { this.setMicroseconds(microseconds); }
|
||||
|
||||
newDate.getUTCMicroseconds = function() { return this.getMicroseconds(); }
|
||||
|
||||
newDate.microseconds = null;
|
||||
newDate.microEpoch = null;
|
||||
newDate.update(microEpoch);
|
||||
return newDate;
|
||||
}
|
||||
|
||||
// Returns a string with the date d formatted according to fmt.
|
||||
// A subset of the Open Group's strftime format is supported.
|
||||
|
||||
function formatDate(d, fmt, monthNames, dayNames) {
|
||||
if (typeof d.strftime === "function") {
|
||||
return d.strftime(fmt);
|
||||
}
|
||||
|
||||
var leftPad = function(n, pad) {
|
||||
n = "" + n;
|
||||
pad = "" + (pad == null ? "0" : pad);
|
||||
return n.length == 1 ? pad + n : n;
|
||||
};
|
||||
|
||||
var formatSubSeconds = function(milliseconds, microseconds, numberDecimalPlaces) {
|
||||
var totalMicroseconds = milliseconds * 1000 + microseconds;
|
||||
var formattedString;
|
||||
if (numberDecimalPlaces < 6 && numberDecimalPlaces > 0) {
|
||||
var magnitude = parseFloat('1e' + (numberDecimalPlaces - 6));
|
||||
totalMicroseconds = Math.round(Math.round(totalMicroseconds * magnitude) / magnitude);
|
||||
formattedString = ('00000' + totalMicroseconds).slice(-6,-(6 - numberDecimalPlaces));
|
||||
} else {
|
||||
totalMicroseconds = Math.round(totalMicroseconds)
|
||||
formattedString = ('00000' + totalMicroseconds).slice(-6);
|
||||
}
|
||||
return formattedString;
|
||||
};
|
||||
|
||||
var r = [];
|
||||
var escape = false;
|
||||
var hours = d.getHours();
|
||||
var isAM = hours < 12;
|
||||
|
||||
if (!monthNames) {
|
||||
monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
}
|
||||
|
||||
if (!dayNames) {
|
||||
dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
}
|
||||
|
||||
var hours12;
|
||||
if (hours > 12) {
|
||||
hours12 = hours - 12;
|
||||
} else if (hours == 0) {
|
||||
hours12 = 12;
|
||||
} else {
|
||||
hours12 = hours;
|
||||
}
|
||||
|
||||
var decimals = -1;
|
||||
for (var i = 0; i < fmt.length; ++i) {
|
||||
var c = fmt.charAt(i);
|
||||
|
||||
if (!isNaN(Number(c)) && Number(c) > 0) {
|
||||
decimals = Number(c);
|
||||
} else if (escape) {
|
||||
switch (c) {
|
||||
case 'a': c = "" + dayNames[d.getDay()]; break;
|
||||
case 'b': c = "" + monthNames[d.getMonth()]; break;
|
||||
case 'd': c = leftPad(d.getDate()); break;
|
||||
case 'e': c = leftPad(d.getDate(), " "); break;
|
||||
case 'h': // For back-compat with 0.7; remove in 1.0
|
||||
case 'H': c = leftPad(hours); break;
|
||||
case 'I': c = leftPad(hours12); break;
|
||||
case 'l': c = leftPad(hours12, " "); break;
|
||||
case 'm': c = leftPad(d.getMonth() + 1); break;
|
||||
case 'M': c = leftPad(d.getMinutes()); break;
|
||||
// quarters not in Open Group's strftime specification
|
||||
case 'q':
|
||||
c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
|
||||
case 'S': c = leftPad(d.getSeconds()); break;
|
||||
case 's': c = "" + formatSubSeconds(d.getMilliseconds(), d.getMicroseconds(), decimals); break;
|
||||
case 'y': c = leftPad(d.getFullYear() % 100); break;
|
||||
case 'Y': c = "" + d.getFullYear(); break;
|
||||
case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
|
||||
case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
|
||||
case 'w': c = "" + d.getDay(); break;
|
||||
}
|
||||
r.push(c);
|
||||
escape = false;
|
||||
} else {
|
||||
if (c == "%") {
|
||||
escape = true;
|
||||
} else {
|
||||
r.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r.join("");
|
||||
}
|
||||
|
||||
// To have a consistent view of time-based data independent of which time
|
||||
// zone the client happens to be in we need a date-like object independent
|
||||
// of time zones. This is done through a wrapper that only calls the UTC
|
||||
// versions of the accessor methods.
|
||||
|
||||
function makeUtcWrapper(d) {
|
||||
function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
|
||||
sourceObj[sourceMethod] = function() {
|
||||
return targetObj[targetMethod].apply(targetObj, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
var utc = {
|
||||
date: d
|
||||
};
|
||||
|
||||
// support strftime, if found
|
||||
if (d.strftime !== undefined) {
|
||||
addProxyMethod(utc, "strftime", d, "strftime");
|
||||
}
|
||||
|
||||
addProxyMethod(utc, "getTime", d, "getTime");
|
||||
addProxyMethod(utc, "setTime", d, "setTime");
|
||||
|
||||
var props = ["Date", "Day", "FullYear", "Hours", "Minutes", "Month", "Seconds", "Milliseconds", "Microseconds"];
|
||||
|
||||
for (var p = 0; p < props.length; p++) {
|
||||
addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
|
||||
addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
|
||||
}
|
||||
|
||||
return utc;
|
||||
}
|
||||
|
||||
// select time zone strategy. This returns a date-like object tied to the
|
||||
// desired timezone
|
||||
function dateGenerator(ts, opts) {
|
||||
var maxDateValue = 8640000000000000;
|
||||
|
||||
if (opts && opts.timeBase === 'seconds') {
|
||||
ts *= 1000;
|
||||
} else if (opts.timeBase === 'microseconds') {
|
||||
ts /= 1000;
|
||||
}
|
||||
|
||||
if (ts > maxDateValue) {
|
||||
ts = maxDateValue;
|
||||
} else if (ts < -maxDateValue) {
|
||||
ts = -maxDateValue;
|
||||
}
|
||||
|
||||
if (opts.timezone === "browser") {
|
||||
return CreateMicroSecondDate(Date, ts);
|
||||
} else if (!opts.timezone || opts.timezone === "utc") {
|
||||
return makeUtcWrapper(CreateMicroSecondDate(Date, ts));
|
||||
} else if (typeof timezoneJS !== "undefined" && typeof timezoneJS.Date !== "undefined") {
|
||||
var d = CreateMicroSecondDate(timezoneJS.Date, ts);
|
||||
// timezone-js is fickle, so be sure to set the time zone before
|
||||
// setting the time.
|
||||
d.setTimezone(opts.timezone);
|
||||
d.setTime(ts);
|
||||
return d;
|
||||
} else {
|
||||
return makeUtcWrapper(CreateMicroSecondDate(Date, ts));
|
||||
}
|
||||
}
|
||||
|
||||
// map of app. size of time units in seconds
|
||||
var timeUnitSizeSeconds = {
|
||||
"microsecond": 0.000001,
|
||||
"millisecond": 0.001,
|
||||
"second": 1,
|
||||
"minute": 60,
|
||||
"hour": 60 * 60,
|
||||
"day": 24 * 60 * 60,
|
||||
"month": 30 * 24 * 60 * 60,
|
||||
"quarter": 3 * 30 * 24 * 60 * 60,
|
||||
"year": 365.2425 * 24 * 60 * 60
|
||||
};
|
||||
|
||||
// map of app. size of time units in milliseconds
|
||||
var timeUnitSizeMilliseconds = {
|
||||
"microsecond": 0.001,
|
||||
"millisecond": 1,
|
||||
"second": 1000,
|
||||
"minute": 60 * 1000,
|
||||
"hour": 60 * 60 * 1000,
|
||||
"day": 24 * 60 * 60 * 1000,
|
||||
"month": 30 * 24 * 60 * 60 * 1000,
|
||||
"quarter": 3 * 30 * 24 * 60 * 60 * 1000,
|
||||
"year": 365.2425 * 24 * 60 * 60 * 1000
|
||||
};
|
||||
|
||||
// map of app. size of time units in microseconds
|
||||
var timeUnitSizeMicroseconds = {
|
||||
"microsecond": 1,
|
||||
"millisecond": 1000,
|
||||
"second": 1000000,
|
||||
"minute": 60 * 1000000,
|
||||
"hour": 60 * 60 * 1000000,
|
||||
"day": 24 * 60 * 60 * 1000000,
|
||||
"month": 30 * 24 * 60 * 60 * 1000000,
|
||||
"quarter": 3 * 30 * 24 * 60 * 60 * 1000000,
|
||||
"year": 365.2425 * 24 * 60 * 60 * 1000000
|
||||
};
|
||||
|
||||
// the allowed tick sizes, after 1 year we use
|
||||
// an integer algorithm
|
||||
|
||||
var baseSpec = [
|
||||
[1, "microsecond"], [2, "microsecond"], [5, "microsecond"], [10, "microsecond"],
|
||||
[25, "microsecond"], [50, "microsecond"], [100, "microsecond"], [250, "microsecond"], [500, "microsecond"],
|
||||
[1, "millisecond"], [2, "millisecond"], [5, "millisecond"], [10, "millisecond"],
|
||||
[25, "millisecond"], [50, "millisecond"], [100, "millisecond"], [250, "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"]
|
||||
];
|
||||
|
||||
// we don't know which variant(s) we'll need yet, but generating both is
|
||||
// cheap
|
||||
|
||||
var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
|
||||
[1, "year"]]);
|
||||
var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
|
||||
[1, "year"]]);
|
||||
|
||||
|
||||
function dateTickGenerator(axis) {
|
||||
var opts = axis.options,
|
||||
ticks = [],
|
||||
d = dateGenerator(axis.min, opts),
|
||||
minSize = 0;
|
||||
|
||||
// make quarter use a possibility if quarters are
|
||||
// mentioned in either of these options
|
||||
var spec = (opts.tickSize && opts.tickSize[1] ===
|
||||
"quarter") ||
|
||||
(opts.minTickSize && opts.minTickSize[1] ===
|
||||
"quarter") ? specQuarters : specMonths;
|
||||
|
||||
var timeUnitSize;
|
||||
if (opts.timeBase === 'seconds') {
|
||||
timeUnitSize = timeUnitSizeSeconds;
|
||||
} else if (opts.timeBase === 'microseconds') {
|
||||
timeUnitSize = timeUnitSizeMicroseconds;
|
||||
} else {
|
||||
timeUnitSize = timeUnitSizeMilliseconds;
|
||||
}
|
||||
|
||||
if (opts.minTickSize !== null && opts.minTickSize !== undefined) {
|
||||
if (typeof opts.tickSize === "number") {
|
||||
minSize = opts.tickSize;
|
||||
} else {
|
||||
minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < spec.length - 1; ++i) {
|
||||
if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] +
|
||||
spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 &&
|
||||
spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var size = spec[i][0];
|
||||
var unit = spec[i][1];
|
||||
// special-case the possibility of several years
|
||||
if (unit === "year") {
|
||||
// if given a minTickSize in years, just use it,
|
||||
// ensuring that it's an integer
|
||||
|
||||
if (opts.minTickSize !== null && opts.minTickSize !== undefined && opts.minTickSize[1] === "year") {
|
||||
size = Math.floor(opts.minTickSize[0]);
|
||||
} else {
|
||||
var magn = parseFloat('1e' + Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
|
||||
var norm = (axis.delta / timeUnitSize.year) / magn;
|
||||
|
||||
if (norm < 1.5) {
|
||||
size = 1;
|
||||
} else if (norm < 3) {
|
||||
size = 2;
|
||||
} else if (norm < 7.5) {
|
||||
size = 5;
|
||||
} else {
|
||||
size = 10;
|
||||
}
|
||||
|
||||
size *= magn;
|
||||
}
|
||||
|
||||
// minimum size for years is 1
|
||||
|
||||
if (size < 1) {
|
||||
size = 1;
|
||||
}
|
||||
}
|
||||
|
||||
axis.tickSize = opts.tickSize || [size, unit];
|
||||
var tickSize = axis.tickSize[0];
|
||||
unit = axis.tickSize[1];
|
||||
|
||||
var step = tickSize * timeUnitSize[unit];
|
||||
|
||||
if (unit === "microsecond") {
|
||||
d.setMicroseconds(floorInBase(d.getMicroseconds(), tickSize));
|
||||
} else if (unit === "millisecond") {
|
||||
d.setMilliseconds(floorInBase(d.getMilliseconds(), tickSize));
|
||||
} else if (unit === "second") {
|
||||
d.setSeconds(floorInBase(d.getSeconds(), tickSize));
|
||||
} else if (unit === "minute") {
|
||||
d.setMinutes(floorInBase(d.getMinutes(), tickSize));
|
||||
} else if (unit === "hour") {
|
||||
d.setHours(floorInBase(d.getHours(), tickSize));
|
||||
} else if (unit === "month") {
|
||||
d.setMonth(floorInBase(d.getMonth(), tickSize));
|
||||
} else if (unit === "quarter") {
|
||||
d.setMonth(3 * floorInBase(d.getMonth() / 3,
|
||||
tickSize));
|
||||
} else if (unit === "year") {
|
||||
d.setFullYear(floorInBase(d.getFullYear(), tickSize));
|
||||
}
|
||||
|
||||
// reset smaller components
|
||||
|
||||
if (step >= timeUnitSize.millisecond) {
|
||||
if (step >= timeUnitSize.second) {
|
||||
d.setMicroseconds(0);
|
||||
} else {
|
||||
d.setMicroseconds(d.getMilliseconds()*1000);
|
||||
}
|
||||
}
|
||||
if (step >= timeUnitSize.minute) {
|
||||
d.setSeconds(0);
|
||||
}
|
||||
if (step >= timeUnitSize.hour) {
|
||||
d.setMinutes(0);
|
||||
}
|
||||
if (step >= timeUnitSize.day) {
|
||||
d.setHours(0);
|
||||
}
|
||||
if (step >= timeUnitSize.day * 4) {
|
||||
d.setDate(1);
|
||||
}
|
||||
if (step >= timeUnitSize.month * 2) {
|
||||
d.setMonth(floorInBase(d.getMonth(), 3));
|
||||
}
|
||||
if (step >= timeUnitSize.quarter * 2) {
|
||||
d.setMonth(floorInBase(d.getMonth(), 6));
|
||||
}
|
||||
if (step >= timeUnitSize.year) {
|
||||
d.setMonth(0);
|
||||
}
|
||||
|
||||
var carry = 0;
|
||||
var v = Number.NaN;
|
||||
var v1000;
|
||||
var prev;
|
||||
do {
|
||||
prev = v;
|
||||
v1000 = d.getTime();
|
||||
if (opts && opts.timeBase === 'seconds') {
|
||||
v = v1000 / 1000;
|
||||
} else if (opts && opts.timeBase === 'microseconds') {
|
||||
v = v1000 * 1000;
|
||||
} else {
|
||||
v = v1000;
|
||||
}
|
||||
|
||||
ticks.push(v);
|
||||
|
||||
if (unit === "month" || unit === "quarter") {
|
||||
if (tickSize < 1) {
|
||||
// a bit complicated - we'll divide the
|
||||
// month/quarter up but we need to take
|
||||
// care of fractions so we don't end up in
|
||||
// the middle of a day
|
||||
d.setDate(1);
|
||||
var start = d.getTime();
|
||||
d.setMonth(d.getMonth() +
|
||||
(unit === "quarter" ? 3 : 1));
|
||||
var end = d.getTime();
|
||||
d.setTime((v + carry * timeUnitSize.hour + (end - start) * tickSize));
|
||||
carry = d.getHours();
|
||||
d.setHours(0);
|
||||
} else {
|
||||
d.setMonth(d.getMonth() +
|
||||
tickSize * (unit === "quarter" ? 3 : 1));
|
||||
}
|
||||
} else if (unit === "year") {
|
||||
d.setFullYear(d.getFullYear() + tickSize);
|
||||
} else {
|
||||
if (opts.timeBase === 'seconds') {
|
||||
d.setTime((v + step) * 1000);
|
||||
} else if (opts.timeBase === 'microseconds') {
|
||||
d.setTime((v + step) / 1000);
|
||||
} else {
|
||||
d.setTime(v + step);
|
||||
}
|
||||
}
|
||||
} while (v < axis.max && v !== prev);
|
||||
|
||||
return ticks;
|
||||
};
|
||||
|
||||
function init(plot) {
|
||||
plot.hooks.processOptions.push(function (plot) {
|
||||
$.each(plot.getAxes(), function(axisName, axis) {
|
||||
var opts = axis.options;
|
||||
if (opts.mode === "time") {
|
||||
axis.tickGenerator = dateTickGenerator;
|
||||
|
||||
axis.tickFormatter = function (v, axis) {
|
||||
var d = dateGenerator(v, axis.options);
|
||||
|
||||
// first check global format
|
||||
if (opts.timeformat != null) {
|
||||
return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
|
||||
}
|
||||
|
||||
// possibly use quarters if quarters are mentioned in
|
||||
// any of these places
|
||||
var useQuarters = (axis.options.tickSize &&
|
||||
axis.options.tickSize[1] == "quarter") ||
|
||||
(axis.options.minTickSize &&
|
||||
axis.options.minTickSize[1] == "quarter");
|
||||
|
||||
var timeUnitSize;
|
||||
if (opts.timeBase === 'seconds') {
|
||||
timeUnitSize = timeUnitSizeSeconds;
|
||||
} else if (opts.timeBase === 'microseconds') {
|
||||
timeUnitSize = timeUnitSizeMicroseconds;
|
||||
} else {
|
||||
timeUnitSize = timeUnitSizeMilliseconds;
|
||||
}
|
||||
|
||||
var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
|
||||
var span = axis.max - axis.min;
|
||||
var suffix = (opts.twelveHourClock) ? " %p" : "";
|
||||
var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
|
||||
var factor;
|
||||
var fmt;
|
||||
|
||||
if (opts.timeBase === 'seconds') {
|
||||
factor = 1;
|
||||
} else if (opts.timeBase === 'microseconds') {
|
||||
factor = 1000000
|
||||
} else {
|
||||
factor = 1000;
|
||||
}
|
||||
|
||||
if (t < timeUnitSize.second) {
|
||||
var decimals = -Math.floor(Math.log10(t/factor))
|
||||
|
||||
// the two-and-halves require an additional decimal
|
||||
if (String(t).indexOf('25') > -1) {
|
||||
decimals++;
|
||||
}
|
||||
|
||||
fmt = "%S.%" + decimals + "s";
|
||||
} else
|
||||
if (t < timeUnitSize.minute) {
|
||||
fmt = hourCode + ":%M:%S" + suffix;
|
||||
} else if (t < timeUnitSize.day) {
|
||||
if (span < 2 * timeUnitSize.day) {
|
||||
fmt = hourCode + ":%M" + suffix;
|
||||
} else {
|
||||
fmt = "%b %d " + hourCode + ":%M" + suffix;
|
||||
}
|
||||
} else if (t < timeUnitSize.month) {
|
||||
fmt = "%b %d";
|
||||
} else if ((useQuarters && t < timeUnitSize.quarter) ||
|
||||
(!useQuarters && t < timeUnitSize.year)) {
|
||||
if (span < timeUnitSize.year) {
|
||||
fmt = "%b";
|
||||
} else {
|
||||
fmt = "%b %Y";
|
||||
}
|
||||
} else if (useQuarters && t < timeUnitSize.year) {
|
||||
if (span < timeUnitSize.year) {
|
||||
fmt = "Q%q";
|
||||
} else {
|
||||
fmt = "Q%q %Y";
|
||||
}
|
||||
} else {
|
||||
fmt = "%Y";
|
||||
}
|
||||
|
||||
var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
|
||||
|
||||
return rt;
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'time',
|
||||
version: '1.0'
|
||||
});
|
||||
|
||||
// Time-axis support used to be in Flot core, which exposed the
|
||||
// formatDate function on the plot object. Various plugins depend
|
||||
// on the function, so we need to re-expose it here.
|
||||
|
||||
$.plot.formatDate = formatDate;
|
||||
$.plot.dateGenerator = dateGenerator;
|
||||
$.plot.dateTickGenerator = dateTickGenerator;
|
||||
$.plot.makeUtcWrapper = makeUtcWrapper;
|
||||
})(jQuery);
|
||||
@@ -1,320 +0,0 @@
|
||||
|
||||
/* global jQuery */
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var options = {
|
||||
propagateSupportedGesture: false
|
||||
};
|
||||
|
||||
function init(plot) {
|
||||
plot.hooks.processOptions.push(initTouchNavigation);
|
||||
}
|
||||
|
||||
function initTouchNavigation(plot, options) {
|
||||
var gestureState = {
|
||||
twoTouches: false,
|
||||
currentTapStart: { x: 0, y: 0 },
|
||||
currentTapEnd: { x: 0, y: 0 },
|
||||
prevTap: { x: 0, y: 0 },
|
||||
currentTap: { x: 0, y: 0 },
|
||||
interceptedLongTap: false,
|
||||
isUnsupportedGesture: false,
|
||||
prevTapTime: null,
|
||||
tapStartTime: null,
|
||||
longTapTriggerId: null
|
||||
},
|
||||
maxDistanceBetweenTaps = 20,
|
||||
maxIntervalBetweenTaps = 500,
|
||||
maxLongTapDistance = 20,
|
||||
minLongTapDuration = 1500,
|
||||
pressedTapDuration = 125,
|
||||
mainEventHolder;
|
||||
|
||||
function interpretGestures(e) {
|
||||
var o = plot.getOptions();
|
||||
|
||||
if (!o.pan.active && !o.zoom.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateOnMultipleTouches(e);
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('touchevent', { detail: e }));
|
||||
|
||||
if (isPinchEvent(e)) {
|
||||
executeAction(e, 'pinch');
|
||||
} else {
|
||||
executeAction(e, 'pan');
|
||||
if (!wasPinchEvent(e)) {
|
||||
if (isDoubleTap(e)) {
|
||||
executeAction(e, 'doubleTap');
|
||||
}
|
||||
executeAction(e, 'tap');
|
||||
executeAction(e, 'longTap');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function executeAction(e, gesture) {
|
||||
switch (gesture) {
|
||||
case 'pan':
|
||||
pan[e.type](e);
|
||||
break;
|
||||
case 'pinch':
|
||||
pinch[e.type](e);
|
||||
break;
|
||||
case 'doubleTap':
|
||||
doubleTap.onDoubleTap(e);
|
||||
break;
|
||||
case 'longTap':
|
||||
longTap[e.type](e);
|
||||
break;
|
||||
case 'tap':
|
||||
tap[e.type](e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents(plot, eventHolder) {
|
||||
mainEventHolder = eventHolder[0];
|
||||
eventHolder[0].addEventListener('touchstart', interpretGestures, false);
|
||||
eventHolder[0].addEventListener('touchmove', interpretGestures, false);
|
||||
eventHolder[0].addEventListener('touchend', interpretGestures, false);
|
||||
}
|
||||
|
||||
function shutdown(plot, eventHolder) {
|
||||
eventHolder[0].removeEventListener('touchstart', interpretGestures);
|
||||
eventHolder[0].removeEventListener('touchmove', interpretGestures);
|
||||
eventHolder[0].removeEventListener('touchend', interpretGestures);
|
||||
if (gestureState.longTapTriggerId) {
|
||||
clearTimeout(gestureState.longTapTriggerId);
|
||||
gestureState.longTapTriggerId = null;
|
||||
}
|
||||
}
|
||||
|
||||
var pan = {
|
||||
touchstart: function(e) {
|
||||
updatePrevForDoubleTap();
|
||||
updateCurrentForDoubleTap(e);
|
||||
updateStateForLongTapStart(e);
|
||||
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));
|
||||
},
|
||||
|
||||
touchmove: function(e) {
|
||||
preventEventBehaviors(e);
|
||||
|
||||
updateCurrentForDoubleTap(e);
|
||||
updateStateForLongTapEnd(e);
|
||||
|
||||
if (!gestureState.isUnsupportedGesture) {
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('pandrag', { detail: e }));
|
||||
}
|
||||
},
|
||||
|
||||
touchend: function(e) {
|
||||
preventEventBehaviors(e);
|
||||
|
||||
if (wasPinchEvent(e)) {
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('pinchend', { detail: e }));
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));
|
||||
} else if (noTouchActive(e)) {
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('panend', { detail: e }));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var pinch = {
|
||||
touchstart: function(e) {
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('pinchstart', { detail: e }));
|
||||
},
|
||||
|
||||
touchmove: function(e) {
|
||||
preventEventBehaviors(e);
|
||||
gestureState.twoTouches = isPinchEvent(e);
|
||||
if (!gestureState.isUnsupportedGesture) {
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('pinchdrag', { detail: e }));
|
||||
}
|
||||
},
|
||||
|
||||
touchend: function(e) {
|
||||
preventEventBehaviors(e);
|
||||
}
|
||||
};
|
||||
|
||||
var doubleTap = {
|
||||
onDoubleTap: function(e) {
|
||||
preventEventBehaviors(e);
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('doubletap', { detail: e }));
|
||||
}
|
||||
};
|
||||
|
||||
var longTap = {
|
||||
touchstart: function(e) {
|
||||
longTap.waitForLongTap(e);
|
||||
},
|
||||
|
||||
touchmove: function(e) {
|
||||
},
|
||||
|
||||
touchend: function(e) {
|
||||
if (gestureState.longTapTriggerId) {
|
||||
clearTimeout(gestureState.longTapTriggerId);
|
||||
gestureState.longTapTriggerId = null;
|
||||
}
|
||||
},
|
||||
|
||||
isLongTap: function(e) {
|
||||
var currentTime = new Date().getTime(),
|
||||
tapDuration = currentTime - gestureState.tapStartTime;
|
||||
if (tapDuration >= minLongTapDuration && !gestureState.interceptedLongTap) {
|
||||
if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
|
||||
gestureState.interceptedLongTap = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
waitForLongTap: function(e) {
|
||||
var longTapTrigger = function() {
|
||||
if (longTap.isLongTap(e)) {
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('longtap', { detail: e }));
|
||||
}
|
||||
gestureState.longTapTriggerId = null;
|
||||
};
|
||||
if (!gestureState.longTapTriggerId) {
|
||||
gestureState.longTapTriggerId = setTimeout(longTapTrigger, minLongTapDuration);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var tap = {
|
||||
touchstart: function(e) {
|
||||
gestureState.tapStartTime = new Date().getTime();
|
||||
},
|
||||
|
||||
touchmove: function(e) {
|
||||
},
|
||||
|
||||
touchend: function(e) {
|
||||
if (tap.isTap(e)) {
|
||||
mainEventHolder.dispatchEvent(new CustomEvent('tap', { detail: e }));
|
||||
preventEventBehaviors(e);
|
||||
}
|
||||
},
|
||||
|
||||
isTap: function(e) {
|
||||
var currentTime = new Date().getTime(),
|
||||
tapDuration = currentTime - gestureState.tapStartTime;
|
||||
if (tapDuration <= pressedTapDuration) {
|
||||
if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (options.pan.enableTouch === true || options.zoom.enableTouch) {
|
||||
plot.hooks.bindEvents.push(bindEvents);
|
||||
plot.hooks.shutdown.push(shutdown);
|
||||
};
|
||||
|
||||
function updatePrevForDoubleTap() {
|
||||
gestureState.prevTap = {
|
||||
x: gestureState.currentTap.x,
|
||||
y: gestureState.currentTap.y
|
||||
};
|
||||
};
|
||||
|
||||
function updateCurrentForDoubleTap(e) {
|
||||
gestureState.currentTap = {
|
||||
x: e.touches[0].pageX,
|
||||
y: e.touches[0].pageY
|
||||
};
|
||||
}
|
||||
|
||||
function updateStateForLongTapStart(e) {
|
||||
gestureState.tapStartTime = new Date().getTime();
|
||||
gestureState.interceptedLongTap = false;
|
||||
gestureState.currentTapStart = {
|
||||
x: e.touches[0].pageX,
|
||||
y: e.touches[0].pageY
|
||||
};
|
||||
gestureState.currentTapEnd = {
|
||||
x: e.touches[0].pageX,
|
||||
y: e.touches[0].pageY
|
||||
};
|
||||
};
|
||||
|
||||
function updateStateForLongTapEnd(e) {
|
||||
gestureState.currentTapEnd = {
|
||||
x: e.touches[0].pageX,
|
||||
y: e.touches[0].pageY
|
||||
};
|
||||
};
|
||||
|
||||
function isDoubleTap(e) {
|
||||
var currentTime = new Date().getTime(),
|
||||
intervalBetweenTaps = currentTime - gestureState.prevTapTime;
|
||||
|
||||
if (intervalBetweenTaps >= 0 && intervalBetweenTaps < maxIntervalBetweenTaps) {
|
||||
if (distance(gestureState.prevTap.x, gestureState.prevTap.y, gestureState.currentTap.x, gestureState.currentTap.y) < maxDistanceBetweenTaps) {
|
||||
e.firstTouch = gestureState.prevTap;
|
||||
e.secondTouch = gestureState.currentTap;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
gestureState.prevTapTime = currentTime;
|
||||
return false;
|
||||
}
|
||||
|
||||
function preventEventBehaviors(e) {
|
||||
if (!gestureState.isUnsupportedGesture) {
|
||||
e.preventDefault();
|
||||
if (!plot.getOptions().propagateSupportedGesture) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function distance(x1, y1, x2, y2) {
|
||||
return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
|
||||
}
|
||||
|
||||
function noTouchActive(e) {
|
||||
return (e.touches && e.touches.length === 0);
|
||||
}
|
||||
|
||||
function wasPinchEvent(e) {
|
||||
return (gestureState.twoTouches && e.touches.length === 1);
|
||||
}
|
||||
|
||||
function updateOnMultipleTouches(e) {
|
||||
if (e.touches.length >= 3) {
|
||||
gestureState.isUnsupportedGesture = true;
|
||||
} else {
|
||||
gestureState.isUnsupportedGesture = false;
|
||||
}
|
||||
}
|
||||
|
||||
function isPinchEvent(e) {
|
||||
if (e.touches && e.touches.length >= 2) {
|
||||
if (e.touches[0].target === plot.getEventHolder() &&
|
||||
e.touches[1].target === plot.getEventHolder()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'navigateTouch',
|
||||
version: '0.3'
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,360 +0,0 @@
|
||||
/* global jQuery */
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var options = {
|
||||
zoom: {
|
||||
enableTouch: false
|
||||
},
|
||||
pan: {
|
||||
enableTouch: false,
|
||||
touchMode: 'manual'
|
||||
},
|
||||
recenter: {
|
||||
enableTouch: true
|
||||
}
|
||||
};
|
||||
|
||||
var ZOOM_DISTANCE_MARGIN = $.plot.uiConstants.ZOOM_DISTANCE_MARGIN;
|
||||
|
||||
function init(plot) {
|
||||
plot.hooks.processOptions.push(initTouchNavigation);
|
||||
}
|
||||
|
||||
function initTouchNavigation(plot, options) {
|
||||
var gestureState = {
|
||||
zoomEnable: false,
|
||||
prevDistance: null,
|
||||
prevTapTime: 0,
|
||||
prevPanPosition: { x: 0, y: 0 },
|
||||
prevTapPosition: { x: 0, y: 0 }
|
||||
},
|
||||
navigationState = {
|
||||
prevTouchedAxis: 'none',
|
||||
currentTouchedAxis: 'none',
|
||||
touchedAxis: null,
|
||||
navigationConstraint: 'unconstrained',
|
||||
initialState: null,
|
||||
},
|
||||
useManualPan = options.pan.interactive && options.pan.touchMode === 'manual',
|
||||
smartPanLock = options.pan.touchMode === 'smartLock',
|
||||
useSmartPan = options.pan.interactive && (smartPanLock || options.pan.touchMode === 'smart'),
|
||||
pan, pinch, doubleTap;
|
||||
|
||||
function bindEvents(plot, eventHolder) {
|
||||
var o = plot.getOptions();
|
||||
|
||||
if (o.zoom.interactive && o.zoom.enableTouch) {
|
||||
eventHolder[0].addEventListener('pinchstart', pinch.start, false);
|
||||
eventHolder[0].addEventListener('pinchdrag', pinch.drag, false);
|
||||
eventHolder[0].addEventListener('pinchend', pinch.end, false);
|
||||
}
|
||||
|
||||
if (o.pan.interactive && o.pan.enableTouch) {
|
||||
eventHolder[0].addEventListener('panstart', pan.start, false);
|
||||
eventHolder[0].addEventListener('pandrag', pan.drag, false);
|
||||
eventHolder[0].addEventListener('panend', pan.end, false);
|
||||
}
|
||||
|
||||
if ((o.recenter.interactive && o.recenter.enableTouch)) {
|
||||
eventHolder[0].addEventListener('doubletap', doubleTap.recenterPlot, false);
|
||||
}
|
||||
}
|
||||
|
||||
function shutdown(plot, eventHolder) {
|
||||
eventHolder[0].removeEventListener('panstart', pan.start);
|
||||
eventHolder[0].removeEventListener('pandrag', pan.drag);
|
||||
eventHolder[0].removeEventListener('panend', pan.end);
|
||||
eventHolder[0].removeEventListener('pinchstart', pinch.start);
|
||||
eventHolder[0].removeEventListener('pinchdrag', pinch.drag);
|
||||
eventHolder[0].removeEventListener('pinchend', pinch.end);
|
||||
eventHolder[0].removeEventListener('doubletap', doubleTap.recenterPlot);
|
||||
}
|
||||
|
||||
pan = {
|
||||
start: function(e) {
|
||||
presetNavigationState(e, 'pan', gestureState);
|
||||
updateData(e, 'pan', gestureState, navigationState);
|
||||
|
||||
if (useSmartPan) {
|
||||
var point = getPoint(e, 'pan');
|
||||
navigationState.initialState = plot.navigationState(point.x, point.y);
|
||||
}
|
||||
},
|
||||
|
||||
drag: function(e) {
|
||||
presetNavigationState(e, 'pan', gestureState);
|
||||
|
||||
if (useSmartPan) {
|
||||
var point = getPoint(e, 'pan');
|
||||
plot.smartPan({
|
||||
x: navigationState.initialState.startPageX - point.x,
|
||||
y: navigationState.initialState.startPageY - point.y
|
||||
}, navigationState.initialState, navigationState.touchedAxis, false, smartPanLock);
|
||||
} else if (useManualPan) {
|
||||
plot.pan({
|
||||
left: -delta(e, 'pan', gestureState).x,
|
||||
top: -delta(e, 'pan', gestureState).y,
|
||||
axes: navigationState.touchedAxis
|
||||
});
|
||||
updatePrevPanPosition(e, 'pan', gestureState, navigationState);
|
||||
}
|
||||
},
|
||||
|
||||
end: function(e) {
|
||||
presetNavigationState(e, 'pan', gestureState);
|
||||
|
||||
if (useSmartPan) {
|
||||
plot.smartPan.end();
|
||||
}
|
||||
|
||||
if (wasPinchEvent(e, gestureState)) {
|
||||
updateprevPanPosition(e, 'pan', gestureState, navigationState);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var pinchDragTimeout;
|
||||
pinch = {
|
||||
start: function(e) {
|
||||
if (pinchDragTimeout) {
|
||||
clearTimeout(pinchDragTimeout);
|
||||
pinchDragTimeout = null;
|
||||
}
|
||||
presetNavigationState(e, 'pinch', gestureState);
|
||||
setPrevDistance(e, gestureState);
|
||||
updateData(e, 'pinch', gestureState, navigationState);
|
||||
},
|
||||
|
||||
drag: function(e) {
|
||||
if (pinchDragTimeout) {
|
||||
return;
|
||||
}
|
||||
pinchDragTimeout = setTimeout(function() {
|
||||
presetNavigationState(e, 'pinch', gestureState);
|
||||
plot.pan({
|
||||
left: -delta(e, 'pinch', gestureState).x,
|
||||
top: -delta(e, 'pinch', gestureState).y,
|
||||
axes: navigationState.touchedAxis
|
||||
});
|
||||
updatePrevPanPosition(e, 'pinch', gestureState, navigationState);
|
||||
|
||||
var dist = pinchDistance(e);
|
||||
|
||||
if (gestureState.zoomEnable || Math.abs(dist - gestureState.prevDistance) > ZOOM_DISTANCE_MARGIN) {
|
||||
zoomPlot(plot, e, gestureState, navigationState);
|
||||
|
||||
//activate zoom mode
|
||||
gestureState.zoomEnable = true;
|
||||
}
|
||||
pinchDragTimeout = null;
|
||||
}, 1000 / 60);
|
||||
},
|
||||
|
||||
end: function(e) {
|
||||
if (pinchDragTimeout) {
|
||||
clearTimeout(pinchDragTimeout);
|
||||
pinchDragTimeout = null;
|
||||
}
|
||||
presetNavigationState(e, 'pinch', gestureState);
|
||||
gestureState.prevDistance = null;
|
||||
}
|
||||
};
|
||||
|
||||
doubleTap = {
|
||||
recenterPlot: function(e) {
|
||||
if (e && e.detail && e.detail.type === 'touchstart') {
|
||||
// only do not recenter for touch start;
|
||||
recenterPlotOnDoubleTap(plot, e, gestureState, navigationState);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (options.pan.enableTouch === true || options.zoom.enableTouch === true) {
|
||||
plot.hooks.bindEvents.push(bindEvents);
|
||||
plot.hooks.shutdown.push(shutdown);
|
||||
}
|
||||
|
||||
function presetNavigationState(e, gesture, gestureState) {
|
||||
navigationState.touchedAxis = getAxis(plot, e, gesture, navigationState);
|
||||
if (noAxisTouched(navigationState)) {
|
||||
navigationState.navigationConstraint = 'unconstrained';
|
||||
} else {
|
||||
navigationState.navigationConstraint = 'axisConstrained';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'navigateTouch',
|
||||
version: '0.3'
|
||||
});
|
||||
|
||||
function recenterPlotOnDoubleTap(plot, e, gestureState, navigationState) {
|
||||
checkAxesForDoubleTap(plot, e, navigationState);
|
||||
if ((navigationState.currentTouchedAxis === 'x' && navigationState.prevTouchedAxis === 'x') ||
|
||||
(navigationState.currentTouchedAxis === 'y' && navigationState.prevTouchedAxis === 'y') ||
|
||||
(navigationState.currentTouchedAxis === 'none' && navigationState.prevTouchedAxis === 'none')) {
|
||||
var event;
|
||||
|
||||
plot.recenter({ axes: navigationState.touchedAxis });
|
||||
|
||||
if (navigationState.touchedAxis) {
|
||||
event = new $.Event('re-center', { detail: { axisTouched: navigationState.touchedAxis } });
|
||||
} else {
|
||||
event = new $.Event('re-center', { detail: e });
|
||||
}
|
||||
plot.getPlaceholder().trigger(event);
|
||||
}
|
||||
}
|
||||
|
||||
function checkAxesForDoubleTap(plot, e, navigationState) {
|
||||
var axis = plot.getTouchedAxis(e.detail.firstTouch.x, e.detail.firstTouch.y);
|
||||
if (axis[0] !== undefined) {
|
||||
navigationState.prevTouchedAxis = axis[0].direction;
|
||||
}
|
||||
|
||||
axis = plot.getTouchedAxis(e.detail.secondTouch.x, e.detail.secondTouch.y);
|
||||
if (axis[0] !== undefined) {
|
||||
navigationState.touchedAxis = axis;
|
||||
navigationState.currentTouchedAxis = axis[0].direction;
|
||||
}
|
||||
|
||||
if (noAxisTouched(navigationState)) {
|
||||
navigationState.touchedAxis = null;
|
||||
navigationState.prevTouchedAxis = 'none';
|
||||
navigationState.currentTouchedAxis = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function zoomPlot(plot, e, gestureState, navigationState) {
|
||||
var offset = plot.offset(),
|
||||
center = {
|
||||
left: 0,
|
||||
top: 0
|
||||
},
|
||||
zoomAmount = pinchDistance(e) / gestureState.prevDistance,
|
||||
dist = pinchDistance(e);
|
||||
|
||||
center.left = getPoint(e, 'pinch').x - offset.left;
|
||||
center.top = getPoint(e, 'pinch').y - offset.top;
|
||||
|
||||
// send the computed touched axis to the zoom function so that it only zooms on that one
|
||||
plot.zoom({
|
||||
center: center,
|
||||
amount: zoomAmount,
|
||||
axes: navigationState.touchedAxis
|
||||
});
|
||||
gestureState.prevDistance = dist;
|
||||
}
|
||||
|
||||
function wasPinchEvent(e, gestureState) {
|
||||
return (gestureState.zoomEnable && e.detail.touches.length === 1);
|
||||
}
|
||||
|
||||
function getAxis(plot, e, gesture, navigationState) {
|
||||
if (e.type === 'pinchstart') {
|
||||
var axisTouch1 = plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);
|
||||
var axisTouch2 = plot.getTouchedAxis(e.detail.touches[1].pageX, e.detail.touches[1].pageY);
|
||||
|
||||
if (axisTouch1.length === axisTouch2.length && axisTouch1.toString() === axisTouch2.toString()) {
|
||||
return axisTouch1;
|
||||
}
|
||||
} else if (e.type === 'panstart') {
|
||||
return plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);
|
||||
} else if (e.type === 'pinchend') {
|
||||
//update axis since instead on pinch, a pan event is made
|
||||
return plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);
|
||||
} else {
|
||||
return navigationState.touchedAxis;
|
||||
}
|
||||
}
|
||||
|
||||
function noAxisTouched(navigationState) {
|
||||
return (!navigationState.touchedAxis || navigationState.touchedAxis.length === 0);
|
||||
}
|
||||
|
||||
function setPrevDistance(e, gestureState) {
|
||||
gestureState.prevDistance = pinchDistance(e);
|
||||
}
|
||||
|
||||
function updateData(e, gesture, gestureState, navigationState) {
|
||||
var axisDir,
|
||||
point = getPoint(e, gesture);
|
||||
|
||||
switch (navigationState.navigationConstraint) {
|
||||
case 'unconstrained':
|
||||
navigationState.touchedAxis = null;
|
||||
gestureState.prevTapPosition = {
|
||||
x: gestureState.prevPanPosition.x,
|
||||
y: gestureState.prevPanPosition.y
|
||||
};
|
||||
gestureState.prevPanPosition = {
|
||||
x: point.x,
|
||||
y: point.y
|
||||
};
|
||||
break;
|
||||
case 'axisConstrained':
|
||||
axisDir = navigationState.touchedAxis[0].direction;
|
||||
navigationState.currentTouchedAxis = axisDir;
|
||||
gestureState.prevTapPosition[axisDir] = gestureState.prevPanPosition[axisDir];
|
||||
gestureState.prevPanPosition[axisDir] = point[axisDir];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function distance(x1, y1, x2, y2) {
|
||||
return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
|
||||
}
|
||||
|
||||
function pinchDistance(e) {
|
||||
var t1 = e.detail.touches[0],
|
||||
t2 = e.detail.touches[1];
|
||||
return distance(t1.pageX, t1.pageY, t2.pageX, t2.pageY);
|
||||
}
|
||||
|
||||
function updatePrevPanPosition(e, gesture, gestureState, navigationState) {
|
||||
var point = getPoint(e, gesture);
|
||||
|
||||
switch (navigationState.navigationConstraint) {
|
||||
case 'unconstrained':
|
||||
gestureState.prevPanPosition.x = point.x;
|
||||
gestureState.prevPanPosition.y = point.y;
|
||||
break;
|
||||
case 'axisConstrained':
|
||||
gestureState.prevPanPosition[navigationState.currentTouchedAxis] =
|
||||
point[navigationState.currentTouchedAxis];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function delta(e, gesture, gestureState) {
|
||||
var point = getPoint(e, gesture);
|
||||
|
||||
return {
|
||||
x: point.x - gestureState.prevPanPosition.x,
|
||||
y: point.y - gestureState.prevPanPosition.y
|
||||
}
|
||||
}
|
||||
|
||||
function getPoint(e, gesture) {
|
||||
if (gesture === 'pinch') {
|
||||
return {
|
||||
x: (e.detail.touches[0].pageX + e.detail.touches[1].pageX) / 2,
|
||||
y: (e.detail.touches[0].pageY + e.detail.touches[1].pageY) / 2
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
x: e.detail.touches[0].pageX,
|
||||
y: e.detail.touches[0].pageY
|
||||
}
|
||||
}
|
||||
}
|
||||
})(jQuery);
|
||||
@@ -1,10 +0,0 @@
|
||||
(function ($) {
|
||||
'use strict';
|
||||
$.plot.uiConstants = {
|
||||
SNAPPING_CONSTANT: 20,
|
||||
PANHINT_LENGTH_CONSTANT: 10,
|
||||
MINOR_TICKS_COUNT_CONSTANT: 4,
|
||||
TICK_LENGTH_CONSTANT: 10,
|
||||
ZOOM_DISTANCE_MARGIN: 25
|
||||
};
|
||||
})(jQuery);
|
||||
|
Before Width: | Height: | Size: 136 KiB |
@@ -1,69 +0,0 @@
|
||||
body {
|
||||
background: rgb(2,0,36);
|
||||
background: linear-gradient(138deg, rgba(2,0,36,1) 0%, rgba(5,5,209,1) 51%, rgba(0,212,255,1) 100%);
|
||||
color: white;
|
||||
}
|
||||
#A {
|
||||
background-color: #7e00db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#B {
|
||||
background-color: #2300ff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#C {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#D {
|
||||
background-color: #00eaff;
|
||||
}
|
||||
|
||||
#E {
|
||||
background-color: #00ff00;
|
||||
}
|
||||
|
||||
#F {
|
||||
background-color: #70ff00;
|
||||
}
|
||||
|
||||
#G {
|
||||
background-color: #c3ff00;
|
||||
}
|
||||
|
||||
#H {
|
||||
background-color: #ffef00;
|
||||
}
|
||||
|
||||
#R {
|
||||
background-color: #ff9b00;
|
||||
}
|
||||
|
||||
#I, #S {
|
||||
background-color: #ff0000;
|
||||
}
|
||||
|
||||
#J {
|
||||
background-color: #f60000;
|
||||
}
|
||||
|
||||
#T {
|
||||
background-color: #c80000;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#U {
|
||||
background-color: #8d0000;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.table-secondary {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.flot-tick-label {
|
||||
fill: white;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
site_name: TeraHz Documentation
|
||||
theme: readthedocs
|
||||
nav:
|
||||
- Start page: 'index.md'
|
||||
- Advanced guides:
|
||||
- Build guide: 'build.md'
|
||||
- Developer's guide: 'dev-guide.md'
|
||||
- Electrical connections: 'electrical.md'
|
||||
- Latest dependencies: 'dependencies.md'
|
||||
@@ -1,3 +0,0 @@
|
||||
version: 2
|
||||
mkdocs:
|
||||
configuration: mkdocs.yml
|
||||
@@ -1,131 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:p="http://www.evolus.vn/Namespace/Pencil" xmlns:pencil="http://www.evolus.vn/Namespace/Pencil" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:rdfa="http://docs.oasis-open.org/opendocument/meta/rdfa#" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:em="http://exslt.org/math" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="1385" height="852" id="exportedSVG" version="1.1" pencil:version="1.2.2" sodipodi:docname="demo"><g inkscape:label="Untitled Page" inkscape:groupmode="layer" id="layer_untitled_page"><g><rect x="0" y="0" width="1385" height="852" fill="none"/><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="316ea25d69264185809ddf96933f1b9d" transform="matrix(1,0,0,1,479,239)"><p:metadata><p:property name="box">379,359</p:property><p:property name="textPadding">18.95,23.933333333333334</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent"/><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="9f2cd04595654a2daa5c78cc57fc3ad1" transform="translate(0.5,0.5)" d="M 0 0 C 126 -1 253 -1 379 0 C 381 120 381 239 379 359 C 253 360 126 360 0 359 C -2 239 -2 120 0 0 z"/>
|
||||
<text p:name="text" id="22d3cf61288a4e6f8aea91fd9f11d090" transform="translate(190,180)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"/>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:Bitmap" id="f2992ac1faff43ef857d97b6097a3377" transform="matrix(1,0,0,1,545,295)"><p:metadata><p:property name="box">248,248</p:property><p:property name="imageData">1600,1600,ref://c549d0e3af32443d81f8de3a764ef050.png</p:property><p:property name="withBlur">false</p:property><p:property name="fillColor">#FFFFFF00</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">0|</p:property></p:metadata>
|
||||
<defs>
|
||||
<filter height="1.2558399" y="-0.12792" width="1.06396" x="-0.03198" p:name="imageShading" id="47f2743a5502445daeea6763b1824d99">
|
||||
<feGaussianBlur stdDeviation="1.3325" in="SourceAlpha"/>
|
||||
</filter>
|
||||
<g p:name="container" id="c807f25ef7cd4046b815700f4e8dd837">
|
||||
<rect style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 0; fill-opacity: 0;" p:name="bgRect" id="442171111cd644568d10f9aaa8367c03" transform="translate(0,0)" width="248" height="248"/>
|
||||
<g p:name="imageContainer" id="149b425645654fd99d93a63c39be92cd" transform="scale(0.155,0.155)">
|
||||
<image x="0" y="0" p:name="image" id="6f19a34b96bf4773969f6d58610f56bd" xlink:href="file:///tmp/tmp-27399CzGZUH8OkSfx/refs/c549d0e3af32443d81f8de3a764ef050.png?token=1549194154000" width="1600" height="1600"/>
|
||||
</g>
|
||||
</g>
|
||||
</defs>
|
||||
<use xlink:href="#c807f25ef7cd4046b815700f4e8dd837" transform="translate(1, 1)" p:filter="url(#47f2743a5502445daeea6763b1824d99)" style="opacity: 0.6; visibility: hidden; display: none;" p:name="bgCopy" id="f490b3b5a7e340a89dd6f4e992753bb7"/>
|
||||
<use xlink:href="#c807f25ef7cd4046b815700f4e8dd837"/>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:PlainTextV2" p:sc="Label" id="d874a4977f474fdeb875d7e6bf5944ed" transform="matrix(1,0,0,1,596,289)"><p:metadata><p:property name="disabled">false</p:property><p:property name="width">100,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="label">Raspberry Pi 3 B+</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|18px|none</p:property><p:property name="textAlign">0,0</p:property></p:metadata>
|
||||
<rect x="0" y="0" style="fill: none; stroke: none; visibility: hidden; display: none;" p:name="bgRect" id="de7b105d7eec4d18b5eb971d7883baaa" width="147.640625" height="25.09375"/>
|
||||
<text xml:space="preserve" p:name="text" id="949a08b7fd4044b794cc5b69b6f83422" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Comic Sans MS"; font-size: 18px; font-weight: normal; font-style: normal; text-decoration: none;"><tspan x="0" y="0">Raspberry Pi 3 B+</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="4e6d928174bb44b2b165b10e439c8069" transform="matrix(1,0,0,1,215,144)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">AS7265X
|
||||
Spektrometerski senzor za IR in vidno svetlobo</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="ba226d4a59e9445cb033b5f6cda2407c" transform="translate(0.5,0.5)" d="M 0 0 C 67 1 133 1 200 0 C 200 50 200 100 200 150 C 133 148 67 148 0 150 C -1 100 -1 50 0 0 z"/>
|
||||
<text p:name="text" id="2f20051133e249f78ca5e893035376d7" transform="translate(10,64)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="62.4609375" y="0">AS7265X</tspan><tspan x="5.109375" y="16">Spektrometerski senzor za IR</tspan><tspan x="43.7109375" y="32.390625">in vidno svetlobo</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="0788d286529c4856817fe87c11c8c576" transform="matrix(1,0,0,1,113,331)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">VEML6075
|
||||
Senzor UV svetlobe</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="fb336ddc2a0e4285ab16ca92def9a93f" transform="translate(0.5,0.5)" d="M 0 0 C 67 2 133 2 200 0 C 199 50 199 100 200 150 C 133 151 67 151 0 150 C -1 100 -1 50 0 0 z"/>
|
||||
<text p:name="text" id="5a27b79034a142a182fbba178737c976" transform="translate(10,72)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="59.109375" y="0">VEML6075</tspan><tspan x="34.640625" y="16">Senzor UV svetlobe</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="fdd2989c419f44e49052af8a9433398b" transform="matrix(1,0,0,1,112,499)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">APDS-9301
|
||||
Merilec osvetljenosti
|
||||
(lux-meter)</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="fd47ea2f4fea4465b5145099e062a50f" transform="translate(0.5,0.5)" d="M 0 0 C 67 0 133 0 200 0 C 198 50 198 100 200 150 C 133 151 67 151 0 150 C 1 100 1 50 0 0 z"/>
|
||||
<text p:name="text" id="3a1e6ef59bfe4e32a29f2d83e8a0eda4" transform="translate(10,64)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="57.8125" y="0">APDS-9301</tspan><tspan x="30.8671875" y="16">Merilec osvetljenosti</tspan><tspan x="57.859375" y="32.484375">(lux-meter)</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="6a62643b2a9e40ad92de2807a3b4d499" transform="matrix(1,0,0,1,898,448)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">2600 mAh Powerbank
|
||||
Napaja spektrometer</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="0e72ea3ae7374eae96f6d39616ad398b" transform="translate(0.5,0.5)" d="M 0 0 C 67 -1 133 -1 200 0 C 200 50 200 100 200 150 C 133 150 67 150 0 150 C 0 100 0 50 0 0 z"/>
|
||||
<text p:name="text" id="e73d9b50f41c4e3eb2efffdb7a5f58d9" transform="translate(10,72)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="29.6875" y="0">2600 mAh Powerbank</tspan><tspan x="30.125" y="16">Napaja spektrometer</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="bc03848225fa44a3a7c3339794d1aa4e" transform="matrix(1,0,0,1,479,127)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:viay="92" ns9889235:viax="-54" ns9889235:connectedOutletId="middle-right" ns9889235:connectedShapeId="4e6d928174bb44b2b165b10e439c8069">-64,92</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:viay="102" ns9889235:viax="189.5" ns9889235:connectedOutletId="top-center" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d">189.5,112</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="590e362dc0a941558c8729fc4369cced" d="M -58 98 L -64 92 L -58 86 M -64 92 C -4 92 190 52 189.5 112 M 195 106 L 189.5 112 L 184 106"/>
|
||||
</defs>
|
||||
<use xlink:href="#590e362dc0a941558c8729fc4369cced" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#590e362dc0a941558c8729fc4369cced" p:name="outArrow1" id="9c47d44f84ff4e8c9c60f2427e5649e4" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
|
||||
<text p:name="text" id="2027c38e59a7415ea356985839c3cb6a" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#590e362dc0a941558c8729fc4369cced" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="7e3a712874e84610942b4fa041b7a43b" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="3f86fad121f34239a0d9a0381b8e5293" transform="matrix(1,0,0,1,339,331)"><p:metadata><p:property name="box">76,316</p:property><p:property name="textPadding">3.8,21.066666666666663</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">I2C
|
||||
Vodilo</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="38738b385aac4e4f882b7957ae3baf95" transform="translate(0.5,0.5)" d="M 0 0 C 25 -1 51 -1 76 0 C 76 105 76 211 76 316 C 51 315 25 315 0 316 C -1 211 -1 105 0 0 z"/>
|
||||
<text p:name="text" id="d829667556ac42128e839eeb4432aec3" transform="translate(4,155)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="23.645312500000003" y="0">I2C</tspan><tspan x="17.145312500000003" y="16">Vodilo</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="e6e45b080a4640e2af58d063d5abe1ef" transform="matrix(1,0,0,1,562,696)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="0788d286529c4856817fe87c11c8c576" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-239" ns9889235:viay="-290">-249,-290</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-left" ns9889235:viax="-233" ns9889235:viay="-207">-223,-207</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="564ef2543bfd4d4f8bf59146b4632c7c" d="M -243 -284 L -249 -290 L -243 -296 M -249 -290 C -206 -290 -266 -207 -223 -207 M -229 -213 L -223 -207 L -229 -201"/>
|
||||
</defs>
|
||||
<use xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" p:name="outArrow1" id="9fd757e4b8d042a09378b6031cfe99e2" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
|
||||
<text p:name="text" id="c73cdd327696416eab5fa6971dba53ef" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="221213c9b3fc487facaa478640fecf0f" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="540bab7bb3f3442f8429144581700e47" transform="matrix(1,0,0,1,324,705)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:viay="-131" ns9889235:viax="-2" ns9889235:connectedOutletId="middle-right" ns9889235:connectedShapeId="fdd2989c419f44e49052af8a9433398b">-12,-131</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-left" ns9889235:viax="5" ns9889235:viay="-216">15,-216</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="b08f88ad83b84d5fad883f5b2d7152d9" d="M -6 -125 L -12 -131 L -6 -137 M -12 -131 C 33 -131 -30 -216 15 -216 M 9 -222 L 15 -216 L 9 -210"/>
|
||||
</defs>
|
||||
<use xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" p:name="outArrow1" id="81869e15f13e41609130f9b556eef9d3" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
|
||||
<text p:name="text" id="2fa0348639be49eda6fa12a65623e9b6" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="c544ec23fdcd4df998935ca8bb683723" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="0bc4ad3e83614786a0f2348a47ec40f2" transform="matrix(1,0,0,1,463,627)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-38" ns9889235:viay="-138">-48,-138</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d" ns9889235:connectedOutletId="middle-left" ns9889235:viax="5.999999999999972" ns9889235:viay="-208.5">16,-208</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="6c567c02fe2642e19f2b63f417f770a9" d="M -42 -132 L -48 -138 L -42 -144 M -48 -138 C -1 -138 -31 -210 16 -208 M 11 -214 L 16 -208 L 10 -203"/>
|
||||
</defs>
|
||||
<use xlink:href="#6c567c02fe2642e19f2b63f417f770a9" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#6c567c02fe2642e19f2b63f417f770a9" p:name="outArrow1" id="cad90b7ac097400b8a9febcd1059b193" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
|
||||
<text p:name="text" id="7256ea09df6f4b7ebf33202f1ced446d" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#6c567c02fe2642e19f2b63f417f770a9" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="a3e251b119324498b6079771d87cf26b" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:RichTextBoxV2" id="f9704a7a815d4cdba445660a10c7ddc8" transform="matrix(1,0,0,1,479,601)"><p:metadata><p:property name="width">200,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="textContent"><span xmlns="http://www.w3.org/1999/xhtml" style="font-family: &quot;Comic Sans MS&quot;;">Računalnik na tiskanem vezju (SBC = single board computer)<br xmlns="http://www.w3.org/1999/xhtml" />Opravlja prevzem in obdelavo podatkov s senzorja, in jih preda telefonu, ki upravlja spektrmeter</span></p:property><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="customStyle">
|
||||
</p:property></p:metadata>
|
||||
|
||||
<foreignObject x="0" y="0" width="585" height="38" p:name="htmlObject" id="54de4aab4c144b8ead414b2e22841369" style="color: rgb(0, 0, 0); font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" p:name="textDiv" id="0cbc982da2c54d4aa9a65d4f6e9727c6" style="display: inline-block; white-space: nowrap; text-decoration: none;"><div><span style="font-family: "Comic Sans MS";">Računalnik na tiskanem vezju (SBC = single board computer)<br />Opravlja prevzem in obdelavo podatkov s senzorja, in jih preda telefonu, ki upravlja spektrmeter</span></div></div>
|
||||
</foreignObject>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="c884a2093fdc477bb3a74e719a1c7c77" transform="matrix(1,0,0,1,951,527)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-83" ns9889235:viay="-108.5">-93,-108</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:viay="-4" ns9889235:viax="-63.000000000000014" ns9889235:connectedOutletId="middle-left" ns9889235:connectedShapeId="6a62643b2a9e40ad92de2807a3b4d499">-53,-4</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="384134701047435fac995fc6eaee5932" d="M -87 -102 L -93 -108 L -87 -114 M -93 -108 C -37 -108 -109 -4 -53 -4 M -59 -10 L -53 -4 L -59 2"/>
|
||||
</defs>
|
||||
<use xlink:href="#384134701047435fac995fc6eaee5932" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#384134701047435fac995fc6eaee5932" p:name="outArrow1" id="14d921eb06d44f7d9acd062d2b01551a" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
|
||||
<text p:name="text" id="5485420a3edb40669c089909c03dc1f8" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#384134701047435fac995fc6eaee5932" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="428895865f0145dca4a5ff61843e80ef" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:Bitmap" id="b6ed13921fdd40f9a3eb0e1e3d0ecbe6" transform="matrix(1,0,0,1,1103,146)"><p:metadata><p:property name="box">195,276</p:property><p:property name="imageData">318,450,ref://7214557d80314d2ea0269f3a449d0000.png</p:property><p:property name="withBlur">false</p:property><p:property name="fillColor">#FFFFFF00</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">0|</p:property></p:metadata>
|
||||
<defs>
|
||||
<filter height="1.2558399" y="-0.12792" width="1.06396" x="-0.03198" p:name="imageShading" id="45b9efdc21654623a49f9d3d92738b31">
|
||||
<feGaussianBlur stdDeviation="1.3325" in="SourceAlpha"/>
|
||||
</filter>
|
||||
<g p:name="container" id="dc93c38161c8442faea0e63e5cbf5843">
|
||||
<rect style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 0; fill-opacity: 0;" p:name="bgRect" id="62e29bf3081e4449ae9fc80c6c967b71" transform="translate(0,0)" width="195" height="276"/>
|
||||
<g p:name="imageContainer" id="6f7191c994c644f183d02acf013570d0" transform="scale(0.6132075471698113,0.6133333333333333)">
|
||||
<image x="0" y="0" p:name="image" id="a6b907023988404f8a2a4c4daeb1b81e" xlink:href="file:///tmp/tmp-27399CzGZUH8OkSfx/refs/7214557d80314d2ea0269f3a449d0000.png?token=1549194986000" width="318" height="450"/>
|
||||
</g>
|
||||
</g>
|
||||
</defs>
|
||||
<use xlink:href="#dc93c38161c8442faea0e63e5cbf5843" transform="translate(1, 1)" p:filter="url(#45b9efdc21654623a49f9d3d92738b31)" style="opacity: 0.6; visibility: hidden; display: none;" p:name="bgCopy" id="02792f50e6104ccc99ad1f691c8d3a5e"/>
|
||||
<use xlink:href="#dc93c38161c8442faea0e63e5cbf5843"/>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="7b94aab3e1cd494696d3b5cf838dfde6" transform="matrix(1,0,0,1,858,286)"><p:metadata><p:property name="startPin">0,0</p:property><p:property name="endPin">282,-4</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">4|3,3</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="b8c0a2b9697f46288c408540e8af57fa" d="M 11 11 L 0 0 L 11 -11 M 0 0 C 60 0 222 -3 282 -4 M 271 -15 L 282 -4 L 271 7"/>
|
||||
</defs>
|
||||
<use xlink:href="#b8c0a2b9697f46288c408540e8af57fa" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#b8c0a2b9697f46288c408540e8af57fa" p:name="outArrow1" id="97846f435e74416e996116a08ef664e4" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 4; stroke-dasharray: 3, 3;"/>
|
||||
<text p:name="text" id="987b10b79cfa4658917b6bb36a6005fb" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#b8c0a2b9697f46288c408540e8af57fa" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="667515745b224bb79dbd2e134284f6d2" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:PlainTextV2" p:sc="Label" id="bb91f7fb7faf445a834363edc2cb287a" transform="matrix(1,0,0,1,1034,157)"><p:metadata><p:property name="disabled">false</p:property><p:property name="width">100,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="label">Mobilni telefon preko WiFi povezave upravlja spektrometer</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textAlign">0,0</p:property></p:metadata>
|
||||
<rect x="0" y="0" style="fill: none; stroke: none; visibility: hidden; display: none;" p:name="bgRect" id="c2e343a9ad3246cdb047e5e01ee1fb42" width="332.390625" height="16.484375"/>
|
||||
<text xml:space="preserve" p:name="text" id="357a8a3e3c4044bb8518698b7d607b60" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none;"><tspan x="0" y="0">Mobilni telefon preko WiFi povezave upravlja spektrometer</tspan></text>
|
||||
</g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 31 KiB |
@@ -1 +0,0 @@
|
||||
# sysprep - How to prepare a Raspberry Pi 3 B+ to run TeraHz
|
||||
|
Before Width: | Height: | Size: 97 KiB |
@@ -1,45 +0,0 @@
|
||||
# getcdata.py - fetch the calibrated data from the AS7265x module
|
||||
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
|
||||
|
||||
import serial as ser
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import time
|
||||
#global variables
|
||||
uartpath = '/dev/ttyUSB0'
|
||||
uartbaud = 115200
|
||||
uarttout = 5
|
||||
|
||||
wl = [410, 435, 460, 485, 510, 535, 560, 585, 610, 645, 680, 705, 730, 760, 810, 860, 900, 940]
|
||||
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF'] # works, do NOT touch!
|
||||
realorder = [i for i in 'ABCDEFGHRISJTUVWKL']
|
||||
|
||||
|
||||
print('getcdata')
|
||||
print('This utility is part of the TeraHz project')
|
||||
|
||||
wavelens = pd.Series(realorder)
|
||||
|
||||
|
||||
plt.ion()
|
||||
win = plt.figure()
|
||||
spectrum=win.add_subplot(111)
|
||||
|
||||
|
||||
with ser.Serial(uartpath, uartbaud, timeout=uarttout) as sensor:
|
||||
while True:
|
||||
sensor.write(b'ATCDATA\n')
|
||||
rawresp = sensor.readline().decode()
|
||||
# parses, calculates and saves the data
|
||||
response = pd.Series([float(i)/35.0 for i in rawresp[:-3].split(',')], index=responseorder)
|
||||
data = pd.DataFrame(response, index=realorder, columns = ['uW/cm^2']) # puts data into a DataFrame
|
||||
data.insert(0, 'wavelenght', wl) #inserts a legend
|
||||
print(data)
|
||||
spectrum.cla()
|
||||
spectrum.plot(data['wavelenght'], data['uW/cm^2'])
|
||||
spectrum.set_xlabel('Valovna dolžina')
|
||||
spectrum.set_ylabel('uW/cm2')
|
||||
win.canvas.draw()
|
||||
|
||||
time.sleep(0.1)
|
||||
@@ -1,12 +0,0 @@
|
||||
import smbus2
|
||||
|
||||
bus = smbus2.SMBus(1)
|
||||
|
||||
result = bus.read_byte_data(0x39, 0x8a)
|
||||
print('LUX Meter ID = {}'.format(result))
|
||||
|
||||
result = bus.read_word_data(0x10, 0x0c)
|
||||
print('UV sensor ID = {}'.format(result))
|
||||
|
||||
result = bus.read_word_data(0x39, 0xec)
|
||||
print('LUX chan 0 = {}'.format(result))
|
||||
@@ -1,22 +0,0 @@
|
||||
from serial import Serial
|
||||
import tkinter as tk
|
||||
import pandas as pd
|
||||
|
||||
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
|
||||
from matplotlib.backend_bases import key_press_handler
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
uartpath = '/dev/ttyUSB0'
|
||||
uartbaud = 115200
|
||||
uarttout = 5
|
||||
|
||||
wl = [410, 435, 460, 485, 510, 535, 560, 585, 610, 645, 680, 705, 730, 760, 810, 860, 900, 940]
|
||||
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF'] # works, do NOT touch!
|
||||
realorder = [i for i in 'ABCDEFGHRISJTUVWKL']
|
||||
|
||||
root = tk.Tk()
|
||||
root.wm_title('TeraHz Demo')
|
||||
|
||||
fig = Figure(figsize=(5, 4), dpi=100)
|
||||
plot = fig.add_subplot(111)
|
||||
canvas = FigureCanvasTkAgg(fig, master=root)
|
||||
@@ -1,76 +0,0 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
if [ ! "$1" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
VIRTUAL_ENV="/root/projekti/TeraHz/utils/venv"
|
||||
export VIRTUAL_ENV
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
if [ "x(venv) " != x ] ; then
|
||||
PS1="(venv) ${PS1:-}"
|
||||
else
|
||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
||||
else
|
||||
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
||||
fi
|
||||
fi
|
||||
export PS1
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
@@ -1,37 +0,0 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV "/root/projekti/TeraHz/utils/venv"
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
if ("venv" != "") then
|
||||
set env_name = "venv"
|
||||
else
|
||||
if (`basename "VIRTUAL_ENV"` == "__") then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
|
||||
else
|
||||
set env_name = `basename "$VIRTUAL_ENV"`
|
||||
endif
|
||||
endif
|
||||
set prompt = "[$env_name] $prompt"
|
||||
unset env_name
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
@@ -1,75 +0,0 @@
|
||||
# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
|
||||
# you cannot run it directly
|
||||
|
||||
function deactivate -d "Exit virtualenv and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
functions -e fish_prompt
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV "/root/projekti/TeraHz/utils/venv"
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# save the current fish_prompt function as the function _old_fish_prompt
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# with the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command
|
||||
set -l old_status $status
|
||||
|
||||
# Prompt override?
|
||||
if test -n "(venv) "
|
||||
printf "%s%s" "(venv) " (set_color normal)
|
||||
else
|
||||
# ...Otherwise, prepend env
|
||||
set -l _checkbase (basename "$VIRTUAL_ENV")
|
||||
if test $_checkbase = "__"
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
|
||||
else
|
||||
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
|
||||
end
|
||||
end
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from setuptools.command.easy_install import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from setuptools.command.easy_install import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from numpy.f2py.f2py2e import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from numpy.f2py.f2py2e import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from numpy.f2py.f2py2e import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,976 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
#
|
||||
# Very simple serial terminal
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import serial
|
||||
from serial.tools.list_ports import comports
|
||||
from serial.tools import hexlify_codec
|
||||
|
||||
# pylint: disable=wrong-import-order,wrong-import-position
|
||||
|
||||
codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
# pylint: disable=redefined-builtin,invalid-name
|
||||
raw_input = input # in python3 it's "raw"
|
||||
unichr = chr
|
||||
|
||||
|
||||
def key_description(character):
|
||||
"""generate a readable description for a key"""
|
||||
ascii_code = ord(character)
|
||||
if ascii_code < 32:
|
||||
return 'Ctrl+{:c}'.format(ord('@') + ascii_code)
|
||||
else:
|
||||
return repr(character)
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
class ConsoleBase(object):
|
||||
"""OS abstraction for console (input/output codec, no echo)"""
|
||||
|
||||
def __init__(self):
|
||||
if sys.version_info >= (3, 0):
|
||||
self.byte_output = sys.stdout.buffer
|
||||
else:
|
||||
self.byte_output = sys.stdout
|
||||
self.output = sys.stdout
|
||||
|
||||
def setup(self):
|
||||
"""Set console to read single characters, no echo"""
|
||||
|
||||
def cleanup(self):
|
||||
"""Restore default console settings"""
|
||||
|
||||
def getkey(self):
|
||||
"""Read a single key from the console"""
|
||||
return None
|
||||
|
||||
def write_bytes(self, byte_string):
|
||||
"""Write bytes (already encoded)"""
|
||||
self.byte_output.write(byte_string)
|
||||
self.byte_output.flush()
|
||||
|
||||
def write(self, text):
|
||||
"""Write string"""
|
||||
self.output.write(text)
|
||||
self.output.flush()
|
||||
|
||||
def cancel(self):
|
||||
"""Cancel getkey operation"""
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# context manager:
|
||||
# switch terminal temporary to normal mode (e.g. to get user input)
|
||||
|
||||
def __enter__(self):
|
||||
self.cleanup()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.setup()
|
||||
|
||||
|
||||
if os.name == 'nt': # noqa
|
||||
import msvcrt
|
||||
import ctypes
|
||||
|
||||
class Out(object):
|
||||
"""file-like wrapper that uses os.write"""
|
||||
|
||||
def __init__(self, fd):
|
||||
self.fd = fd
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def write(self, s):
|
||||
os.write(self.fd, s)
|
||||
|
||||
class Console(ConsoleBase):
|
||||
def __init__(self):
|
||||
super(Console, self).__init__()
|
||||
self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
|
||||
self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
|
||||
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
|
||||
ctypes.windll.kernel32.SetConsoleCP(65001)
|
||||
self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
|
||||
# the change of the code page is not propagated to Python, manually fix it
|
||||
sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
|
||||
sys.stdout = self.output
|
||||
self.output.encoding = 'UTF-8' # needed for input
|
||||
|
||||
def __del__(self):
|
||||
ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
|
||||
ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
|
||||
|
||||
def getkey(self):
|
||||
while True:
|
||||
z = msvcrt.getwch()
|
||||
if z == unichr(13):
|
||||
return unichr(10)
|
||||
elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
|
||||
msvcrt.getwch()
|
||||
else:
|
||||
return z
|
||||
|
||||
def cancel(self):
|
||||
# CancelIo, CancelSynchronousIo do not seem to work when using
|
||||
# getwch, so instead, send a key to the window with the console
|
||||
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
|
||||
ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)
|
||||
|
||||
elif os.name == 'posix':
|
||||
import atexit
|
||||
import termios
|
||||
import fcntl
|
||||
|
||||
class Console(ConsoleBase):
|
||||
def __init__(self):
|
||||
super(Console, self).__init__()
|
||||
self.fd = sys.stdin.fileno()
|
||||
self.old = termios.tcgetattr(self.fd)
|
||||
atexit.register(self.cleanup)
|
||||
if sys.version_info < (3, 0):
|
||||
self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
|
||||
else:
|
||||
self.enc_stdin = sys.stdin
|
||||
|
||||
def setup(self):
|
||||
new = termios.tcgetattr(self.fd)
|
||||
new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
|
||||
new[6][termios.VMIN] = 1
|
||||
new[6][termios.VTIME] = 0
|
||||
termios.tcsetattr(self.fd, termios.TCSANOW, new)
|
||||
|
||||
def getkey(self):
|
||||
c = self.enc_stdin.read(1)
|
||||
if c == unichr(0x7f):
|
||||
c = unichr(8) # map the BS key (which yields DEL) to backspace
|
||||
return c
|
||||
|
||||
def cancel(self):
|
||||
fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0')
|
||||
|
||||
def cleanup(self):
|
||||
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
class Transform(object):
|
||||
"""do-nothing: forward all data unchanged"""
|
||||
def rx(self, text):
|
||||
"""text received from serial port"""
|
||||
return text
|
||||
|
||||
def tx(self, text):
|
||||
"""text to be sent to serial port"""
|
||||
return text
|
||||
|
||||
def echo(self, text):
|
||||
"""text to be sent but displayed on console"""
|
||||
return text
|
||||
|
||||
|
||||
class CRLF(Transform):
|
||||
"""ENTER sends CR+LF"""
|
||||
|
||||
def tx(self, text):
|
||||
return text.replace('\n', '\r\n')
|
||||
|
||||
|
||||
class CR(Transform):
|
||||
"""ENTER sends CR"""
|
||||
|
||||
def rx(self, text):
|
||||
return text.replace('\r', '\n')
|
||||
|
||||
def tx(self, text):
|
||||
return text.replace('\n', '\r')
|
||||
|
||||
|
||||
class LF(Transform):
|
||||
"""ENTER sends LF"""
|
||||
|
||||
|
||||
class NoTerminal(Transform):
|
||||
"""remove typical terminal control codes from input"""
|
||||
|
||||
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
|
||||
REPLACEMENT_MAP.update(
|
||||
{
|
||||
0x7F: 0x2421, # DEL
|
||||
0x9B: 0x2425, # CSI
|
||||
})
|
||||
|
||||
def rx(self, text):
|
||||
return text.translate(self.REPLACEMENT_MAP)
|
||||
|
||||
echo = rx
|
||||
|
||||
|
||||
class NoControls(NoTerminal):
|
||||
"""Remove all control codes, incl. CR+LF"""
|
||||
|
||||
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
|
||||
REPLACEMENT_MAP.update(
|
||||
{
|
||||
0x20: 0x2423, # visual space
|
||||
0x7F: 0x2421, # DEL
|
||||
0x9B: 0x2425, # CSI
|
||||
})
|
||||
|
||||
|
||||
class Printable(Transform):
|
||||
"""Show decimal code for all non-ASCII characters and replace most control codes"""
|
||||
|
||||
def rx(self, text):
|
||||
r = []
|
||||
for c in text:
|
||||
if ' ' <= c < '\x7f' or c in '\r\n\b\t':
|
||||
r.append(c)
|
||||
elif c < ' ':
|
||||
r.append(unichr(0x2400 + ord(c)))
|
||||
else:
|
||||
r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
|
||||
r.append(' ')
|
||||
return ''.join(r)
|
||||
|
||||
echo = rx
|
||||
|
||||
|
||||
class Colorize(Transform):
|
||||
"""Apply different colors for received and echo"""
|
||||
|
||||
def __init__(self):
|
||||
# XXX make it configurable, use colorama?
|
||||
self.input_color = '\x1b[37m'
|
||||
self.echo_color = '\x1b[31m'
|
||||
|
||||
def rx(self, text):
|
||||
return self.input_color + text
|
||||
|
||||
def echo(self, text):
|
||||
return self.echo_color + text
|
||||
|
||||
|
||||
class DebugIO(Transform):
|
||||
"""Print what is sent and received"""
|
||||
|
||||
def rx(self, text):
|
||||
sys.stderr.write(' [RX:{}] '.format(repr(text)))
|
||||
sys.stderr.flush()
|
||||
return text
|
||||
|
||||
def tx(self, text):
|
||||
sys.stderr.write(' [TX:{}] '.format(repr(text)))
|
||||
sys.stderr.flush()
|
||||
return text
|
||||
|
||||
|
||||
# other ideas:
|
||||
# - add date/time for each newline
|
||||
# - insert newline after: a) timeout b) packet end character
|
||||
|
||||
EOL_TRANSFORMATIONS = {
|
||||
'crlf': CRLF,
|
||||
'cr': CR,
|
||||
'lf': LF,
|
||||
}
|
||||
|
||||
TRANSFORMATIONS = {
|
||||
'direct': Transform, # no transformation
|
||||
'default': NoTerminal,
|
||||
'nocontrol': NoControls,
|
||||
'printable': Printable,
|
||||
'colorize': Colorize,
|
||||
'debug': DebugIO,
|
||||
}
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
def ask_for_port():
|
||||
"""\
|
||||
Show a list of ports and ask the user for a choice. To make selection
|
||||
easier on systems with long device names, also allow the input of an
|
||||
index.
|
||||
"""
|
||||
sys.stderr.write('\n--- Available ports:\n')
|
||||
ports = []
|
||||
for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
|
||||
sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc))
|
||||
ports.append(port)
|
||||
while True:
|
||||
port = raw_input('--- Enter port index or full name: ')
|
||||
try:
|
||||
index = int(port) - 1
|
||||
if not 0 <= index < len(ports):
|
||||
sys.stderr.write('--- Invalid index!\n')
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
port = ports[index]
|
||||
return port
|
||||
|
||||
|
||||
class Miniterm(object):
|
||||
"""\
|
||||
Terminal application. Copy data from serial port to console and vice versa.
|
||||
Handle special keys from the console to show menu etc.
|
||||
"""
|
||||
|
||||
def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
|
||||
self.console = Console()
|
||||
self.serial = serial_instance
|
||||
self.echo = echo
|
||||
self.raw = False
|
||||
self.input_encoding = 'UTF-8'
|
||||
self.output_encoding = 'UTF-8'
|
||||
self.eol = eol
|
||||
self.filters = filters
|
||||
self.update_transformations()
|
||||
self.exit_character = 0x1d # GS/CTRL+]
|
||||
self.menu_character = 0x14 # Menu: CTRL+T
|
||||
self.alive = None
|
||||
self._reader_alive = None
|
||||
self.receiver_thread = None
|
||||
self.rx_decoder = None
|
||||
self.tx_decoder = None
|
||||
|
||||
def _start_reader(self):
|
||||
"""Start reader thread"""
|
||||
self._reader_alive = True
|
||||
# start serial->console thread
|
||||
self.receiver_thread = threading.Thread(target=self.reader, name='rx')
|
||||
self.receiver_thread.daemon = True
|
||||
self.receiver_thread.start()
|
||||
|
||||
def _stop_reader(self):
|
||||
"""Stop reader thread only, wait for clean exit of thread"""
|
||||
self._reader_alive = False
|
||||
if hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.cancel_read()
|
||||
self.receiver_thread.join()
|
||||
|
||||
def start(self):
|
||||
"""start worker threads"""
|
||||
self.alive = True
|
||||
self._start_reader()
|
||||
# enter console->serial loop
|
||||
self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
|
||||
self.transmitter_thread.daemon = True
|
||||
self.transmitter_thread.start()
|
||||
self.console.setup()
|
||||
|
||||
def stop(self):
|
||||
"""set flag to stop worker threads"""
|
||||
self.alive = False
|
||||
|
||||
def join(self, transmit_only=False):
|
||||
"""wait for worker threads to terminate"""
|
||||
self.transmitter_thread.join()
|
||||
if not transmit_only:
|
||||
if hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.cancel_read()
|
||||
self.receiver_thread.join()
|
||||
|
||||
def close(self):
|
||||
self.serial.close()
|
||||
|
||||
def update_transformations(self):
|
||||
"""take list of transformation classes and instantiate them for rx and tx"""
|
||||
transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
|
||||
for f in self.filters]
|
||||
self.tx_transformations = [t() for t in transformations]
|
||||
self.rx_transformations = list(reversed(self.tx_transformations))
|
||||
|
||||
def set_rx_encoding(self, encoding, errors='replace'):
|
||||
"""set encoding for received data"""
|
||||
self.input_encoding = encoding
|
||||
self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
|
||||
|
||||
def set_tx_encoding(self, encoding, errors='replace'):
|
||||
"""set encoding for transmitted data"""
|
||||
self.output_encoding = encoding
|
||||
self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
|
||||
|
||||
def dump_port_settings(self):
|
||||
"""Write current settings to sys.stderr"""
|
||||
sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
|
||||
p=self.serial))
|
||||
sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
|
||||
('active' if self.serial.rts else 'inactive'),
|
||||
('active' if self.serial.dtr else 'inactive'),
|
||||
('active' if self.serial.break_condition else 'inactive')))
|
||||
try:
|
||||
sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
|
||||
('active' if self.serial.cts else 'inactive'),
|
||||
('active' if self.serial.dsr else 'inactive'),
|
||||
('active' if self.serial.ri else 'inactive'),
|
||||
('active' if self.serial.cd else 'inactive')))
|
||||
except serial.SerialException:
|
||||
# on RFC 2217 ports, it can happen if no modem state notification was
|
||||
# yet received. ignore this error.
|
||||
pass
|
||||
sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
|
||||
sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
|
||||
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
|
||||
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
|
||||
sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
|
||||
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
|
||||
|
||||
def reader(self):
|
||||
"""loop and copy serial->console"""
|
||||
try:
|
||||
while self.alive and self._reader_alive:
|
||||
# read all that is there or wait for one byte
|
||||
data = self.serial.read(self.serial.in_waiting or 1)
|
||||
if data:
|
||||
if self.raw:
|
||||
self.console.write_bytes(data)
|
||||
else:
|
||||
text = self.rx_decoder.decode(data)
|
||||
for transformation in self.rx_transformations:
|
||||
text = transformation.rx(text)
|
||||
self.console.write(text)
|
||||
except serial.SerialException:
|
||||
self.alive = False
|
||||
self.console.cancel()
|
||||
raise # XXX handle instead of re-raise?
|
||||
|
||||
def writer(self):
|
||||
"""\
|
||||
Loop and copy console->serial until self.exit_character character is
|
||||
found. When self.menu_character is found, interpret the next key
|
||||
locally.
|
||||
"""
|
||||
menu_active = False
|
||||
try:
|
||||
while self.alive:
|
||||
try:
|
||||
c = self.console.getkey()
|
||||
except KeyboardInterrupt:
|
||||
c = '\x03'
|
||||
if not self.alive:
|
||||
break
|
||||
if menu_active:
|
||||
self.handle_menu_key(c)
|
||||
menu_active = False
|
||||
elif c == self.menu_character:
|
||||
menu_active = True # next char will be for menu
|
||||
elif c == self.exit_character:
|
||||
self.stop() # exit app
|
||||
break
|
||||
else:
|
||||
#~ if self.raw:
|
||||
text = c
|
||||
for transformation in self.tx_transformations:
|
||||
text = transformation.tx(text)
|
||||
self.serial.write(self.tx_encoder.encode(text))
|
||||
if self.echo:
|
||||
echo_text = c
|
||||
for transformation in self.tx_transformations:
|
||||
echo_text = transformation.echo(echo_text)
|
||||
self.console.write(echo_text)
|
||||
except:
|
||||
self.alive = False
|
||||
raise
|
||||
|
||||
def handle_menu_key(self, c):
|
||||
"""Implement a simple menu / settings"""
|
||||
if c == self.menu_character or c == self.exit_character:
|
||||
# Menu/exit character again -> send itself
|
||||
self.serial.write(self.tx_encoder.encode(c))
|
||||
if self.echo:
|
||||
self.console.write(c)
|
||||
elif c == '\x15': # CTRL+U -> upload file
|
||||
self.upload_file()
|
||||
elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
|
||||
sys.stderr.write(self.get_help_text())
|
||||
elif c == '\x12': # CTRL+R -> Toggle RTS
|
||||
self.serial.rts = not self.serial.rts
|
||||
sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
|
||||
elif c == '\x04': # CTRL+D -> Toggle DTR
|
||||
self.serial.dtr = not self.serial.dtr
|
||||
sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
|
||||
elif c == '\x02': # CTRL+B -> toggle BREAK condition
|
||||
self.serial.break_condition = not self.serial.break_condition
|
||||
sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
|
||||
elif c == '\x05': # CTRL+E -> toggle local echo
|
||||
self.echo = not self.echo
|
||||
sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
|
||||
elif c == '\x06': # CTRL+F -> edit filters
|
||||
self.change_filter()
|
||||
elif c == '\x0c': # CTRL+L -> EOL mode
|
||||
modes = list(EOL_TRANSFORMATIONS) # keys
|
||||
eol = modes.index(self.eol) + 1
|
||||
if eol >= len(modes):
|
||||
eol = 0
|
||||
self.eol = modes[eol]
|
||||
sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
|
||||
self.update_transformations()
|
||||
elif c == '\x01': # CTRL+A -> set encoding
|
||||
self.change_encoding()
|
||||
elif c == '\x09': # CTRL+I -> info
|
||||
self.dump_port_settings()
|
||||
#~ elif c == '\x01': # CTRL+A -> cycle escape mode
|
||||
#~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
|
||||
elif c in 'pP': # P -> change port
|
||||
self.change_port()
|
||||
elif c in 'sS': # S -> suspend / open port temporarily
|
||||
self.suspend_port()
|
||||
elif c in 'bB': # B -> change baudrate
|
||||
self.change_baudrate()
|
||||
elif c == '8': # 8 -> change to 8 bits
|
||||
self.serial.bytesize = serial.EIGHTBITS
|
||||
self.dump_port_settings()
|
||||
elif c == '7': # 7 -> change to 8 bits
|
||||
self.serial.bytesize = serial.SEVENBITS
|
||||
self.dump_port_settings()
|
||||
elif c in 'eE': # E -> change to even parity
|
||||
self.serial.parity = serial.PARITY_EVEN
|
||||
self.dump_port_settings()
|
||||
elif c in 'oO': # O -> change to odd parity
|
||||
self.serial.parity = serial.PARITY_ODD
|
||||
self.dump_port_settings()
|
||||
elif c in 'mM': # M -> change to mark parity
|
||||
self.serial.parity = serial.PARITY_MARK
|
||||
self.dump_port_settings()
|
||||
elif c in 'sS': # S -> change to space parity
|
||||
self.serial.parity = serial.PARITY_SPACE
|
||||
self.dump_port_settings()
|
||||
elif c in 'nN': # N -> change to no parity
|
||||
self.serial.parity = serial.PARITY_NONE
|
||||
self.dump_port_settings()
|
||||
elif c == '1': # 1 -> change to 1 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_ONE
|
||||
self.dump_port_settings()
|
||||
elif c == '2': # 2 -> change to 2 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_TWO
|
||||
self.dump_port_settings()
|
||||
elif c == '3': # 3 -> change to 1.5 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
|
||||
self.dump_port_settings()
|
||||
elif c in 'xX': # X -> change software flow control
|
||||
self.serial.xonxoff = (c == 'X')
|
||||
self.dump_port_settings()
|
||||
elif c in 'rR': # R -> change hardware flow control
|
||||
self.serial.rtscts = (c == 'R')
|
||||
self.dump_port_settings()
|
||||
else:
|
||||
sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
|
||||
|
||||
def upload_file(self):
|
||||
"""Ask user for filenname and send its contents"""
|
||||
sys.stderr.write('\n--- File to upload: ')
|
||||
sys.stderr.flush()
|
||||
with self.console:
|
||||
filename = sys.stdin.readline().rstrip('\r\n')
|
||||
if filename:
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
sys.stderr.write('--- Sending file {} ---\n'.format(filename))
|
||||
while True:
|
||||
block = f.read(1024)
|
||||
if not block:
|
||||
break
|
||||
self.serial.write(block)
|
||||
# Wait for output buffer to drain.
|
||||
self.serial.flush()
|
||||
sys.stderr.write('.') # Progress indicator.
|
||||
sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
|
||||
except IOError as e:
|
||||
sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
|
||||
|
||||
def change_filter(self):
|
||||
"""change the i/o transformations"""
|
||||
sys.stderr.write('\n--- Available Filters:\n')
|
||||
sys.stderr.write('\n'.join(
|
||||
'--- {:<10} = {.__doc__}'.format(k, v)
|
||||
for k, v in sorted(TRANSFORMATIONS.items())))
|
||||
sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
|
||||
with self.console:
|
||||
new_filters = sys.stdin.readline().lower().split()
|
||||
if new_filters:
|
||||
for f in new_filters:
|
||||
if f not in TRANSFORMATIONS:
|
||||
sys.stderr.write('--- unknown filter: {}\n'.format(repr(f)))
|
||||
break
|
||||
else:
|
||||
self.filters = new_filters
|
||||
self.update_transformations()
|
||||
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
|
||||
|
||||
def change_encoding(self):
|
||||
"""change encoding on the serial port"""
|
||||
sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
|
||||
with self.console:
|
||||
new_encoding = sys.stdin.readline().strip()
|
||||
if new_encoding:
|
||||
try:
|
||||
codecs.lookup(new_encoding)
|
||||
except LookupError:
|
||||
sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
|
||||
else:
|
||||
self.set_rx_encoding(new_encoding)
|
||||
self.set_tx_encoding(new_encoding)
|
||||
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
|
||||
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
|
||||
|
||||
def change_baudrate(self):
|
||||
"""change the baudrate"""
|
||||
sys.stderr.write('\n--- Baudrate: ')
|
||||
sys.stderr.flush()
|
||||
with self.console:
|
||||
backup = self.serial.baudrate
|
||||
try:
|
||||
self.serial.baudrate = int(sys.stdin.readline().strip())
|
||||
except ValueError as e:
|
||||
sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
|
||||
self.serial.baudrate = backup
|
||||
else:
|
||||
self.dump_port_settings()
|
||||
|
||||
def change_port(self):
|
||||
"""Have a conversation with the user to change the serial port"""
|
||||
with self.console:
|
||||
try:
|
||||
port = ask_for_port()
|
||||
except KeyboardInterrupt:
|
||||
port = None
|
||||
if port and port != self.serial.port:
|
||||
# reader thread needs to be shut down
|
||||
self._stop_reader()
|
||||
# save settings
|
||||
settings = self.serial.getSettingsDict()
|
||||
try:
|
||||
new_serial = serial.serial_for_url(port, do_not_open=True)
|
||||
# restore settings and open
|
||||
new_serial.applySettingsDict(settings)
|
||||
new_serial.rts = self.serial.rts
|
||||
new_serial.dtr = self.serial.dtr
|
||||
new_serial.open()
|
||||
new_serial.break_condition = self.serial.break_condition
|
||||
except Exception as e:
|
||||
sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
|
||||
new_serial.close()
|
||||
else:
|
||||
self.serial.close()
|
||||
self.serial = new_serial
|
||||
sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
|
||||
# and restart the reader thread
|
||||
self._start_reader()
|
||||
|
||||
def suspend_port(self):
|
||||
"""\
|
||||
open port temporarily, allow reconnect, exit and port change to get
|
||||
out of the loop
|
||||
"""
|
||||
# reader thread needs to be shut down
|
||||
self._stop_reader()
|
||||
self.serial.close()
|
||||
sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port))
|
||||
do_change_port = False
|
||||
while not self.serial.is_open:
|
||||
sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format(
|
||||
exit=key_description(self.exit_character)))
|
||||
k = self.console.getkey()
|
||||
if k == self.exit_character:
|
||||
self.stop() # exit app
|
||||
break
|
||||
elif k in 'pP':
|
||||
do_change_port = True
|
||||
break
|
||||
try:
|
||||
self.serial.open()
|
||||
except Exception as e:
|
||||
sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e))
|
||||
if do_change_port:
|
||||
self.change_port()
|
||||
else:
|
||||
# and restart the reader thread
|
||||
self._start_reader()
|
||||
sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port))
|
||||
|
||||
def get_help_text(self):
|
||||
"""return the help text"""
|
||||
# help text, starts with blank line!
|
||||
return """
|
||||
--- pySerial ({version}) - miniterm - help
|
||||
---
|
||||
--- {exit:8} Exit program
|
||||
--- {menu:8} Menu escape key, followed by:
|
||||
--- Menu keys:
|
||||
--- {menu:7} Send the menu character itself to remote
|
||||
--- {exit:7} Send the exit character itself to remote
|
||||
--- {info:7} Show info
|
||||
--- {upload:7} Upload file (prompt will be shown)
|
||||
--- {repr:7} encoding
|
||||
--- {filter:7} edit filters
|
||||
--- Toggles:
|
||||
--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
|
||||
--- {echo:7} echo {eol:7} EOL
|
||||
---
|
||||
--- Port settings ({menu} followed by the following):
|
||||
--- p change port
|
||||
--- 7 8 set data bits
|
||||
--- N E O S M change parity (None, Even, Odd, Space, Mark)
|
||||
--- 1 2 3 set stop bits (1, 2, 1.5)
|
||||
--- b change baud rate
|
||||
--- x X disable/enable software flow control
|
||||
--- r R disable/enable hardware flow control
|
||||
""".format(version=getattr(serial, 'VERSION', 'unknown version'),
|
||||
exit=key_description(self.exit_character),
|
||||
menu=key_description(self.menu_character),
|
||||
rts=key_description('\x12'),
|
||||
dtr=key_description('\x04'),
|
||||
brk=key_description('\x02'),
|
||||
echo=key_description('\x05'),
|
||||
info=key_description('\x09'),
|
||||
upload=key_description('\x15'),
|
||||
repr=key_description('\x01'),
|
||||
filter=key_description('\x06'),
|
||||
eol=key_description('\x0c'))
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# default args can be used to override when calling main() from an other script
|
||||
# e.g to create a miniterm-my-device.py
|
||||
def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
|
||||
"""Command line tool, entry point"""
|
||||
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Miniterm - A simple terminal program for the serial port.")
|
||||
|
||||
parser.add_argument(
|
||||
"port",
|
||||
nargs='?',
|
||||
help="serial port name ('-' to show port list)",
|
||||
default=default_port)
|
||||
|
||||
parser.add_argument(
|
||||
"baudrate",
|
||||
nargs='?',
|
||||
type=int,
|
||||
help="set baud rate, default: %(default)s",
|
||||
default=default_baudrate)
|
||||
|
||||
group = parser.add_argument_group("port settings")
|
||||
|
||||
group.add_argument(
|
||||
"--parity",
|
||||
choices=['N', 'E', 'O', 'S', 'M'],
|
||||
type=lambda c: c.upper(),
|
||||
help="set parity, one of {N E O S M}, default: N",
|
||||
default='N')
|
||||
|
||||
group.add_argument(
|
||||
"--rtscts",
|
||||
action="store_true",
|
||||
help="enable RTS/CTS flow control (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--xonxoff",
|
||||
action="store_true",
|
||||
help="enable software flow control (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--rts",
|
||||
type=int,
|
||||
help="set initial RTS line state (possible values: 0, 1)",
|
||||
default=default_rts)
|
||||
|
||||
group.add_argument(
|
||||
"--dtr",
|
||||
type=int,
|
||||
help="set initial DTR line state (possible values: 0, 1)",
|
||||
default=default_dtr)
|
||||
|
||||
group.add_argument(
|
||||
"--ask",
|
||||
action="store_true",
|
||||
help="ask again for port when open fails",
|
||||
default=False)
|
||||
|
||||
group = parser.add_argument_group("data handling")
|
||||
|
||||
group.add_argument(
|
||||
"-e", "--echo",
|
||||
action="store_true",
|
||||
help="enable local echo (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--encoding",
|
||||
dest="serial_port_encoding",
|
||||
metavar="CODEC",
|
||||
help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
|
||||
default='UTF-8')
|
||||
|
||||
group.add_argument(
|
||||
"-f", "--filter",
|
||||
action="append",
|
||||
metavar="NAME",
|
||||
help="add text transformation",
|
||||
default=[])
|
||||
|
||||
group.add_argument(
|
||||
"--eol",
|
||||
choices=['CR', 'LF', 'CRLF'],
|
||||
type=lambda c: c.upper(),
|
||||
help="end of line mode",
|
||||
default='CRLF')
|
||||
|
||||
group.add_argument(
|
||||
"--raw",
|
||||
action="store_true",
|
||||
help="Do no apply any encodings/transformations",
|
||||
default=False)
|
||||
|
||||
group = parser.add_argument_group("hotkeys")
|
||||
|
||||
group.add_argument(
|
||||
"--exit-char",
|
||||
type=int,
|
||||
metavar='NUM',
|
||||
help="Unicode of special character that is used to exit the application, default: %(default)s",
|
||||
default=0x1d) # GS/CTRL+]
|
||||
|
||||
group.add_argument(
|
||||
"--menu-char",
|
||||
type=int,
|
||||
metavar='NUM',
|
||||
help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
|
||||
default=0x14) # Menu: CTRL+T
|
||||
|
||||
group = parser.add_argument_group("diagnostics")
|
||||
|
||||
group.add_argument(
|
||||
"-q", "--quiet",
|
||||
action="store_true",
|
||||
help="suppress non-error messages",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--develop",
|
||||
action="store_true",
|
||||
help="show Python traceback on error",
|
||||
default=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.menu_char == args.exit_char:
|
||||
parser.error('--exit-char can not be the same as --menu-char')
|
||||
|
||||
if args.filter:
|
||||
if 'help' in args.filter:
|
||||
sys.stderr.write('Available filters:\n')
|
||||
sys.stderr.write('\n'.join(
|
||||
'{:<10} = {.__doc__}'.format(k, v)
|
||||
for k, v in sorted(TRANSFORMATIONS.items())))
|
||||
sys.stderr.write('\n')
|
||||
sys.exit(1)
|
||||
filters = args.filter
|
||||
else:
|
||||
filters = ['default']
|
||||
|
||||
while True:
|
||||
# no port given on command line -> ask user now
|
||||
if args.port is None or args.port == '-':
|
||||
try:
|
||||
args.port = ask_for_port()
|
||||
except KeyboardInterrupt:
|
||||
sys.stderr.write('\n')
|
||||
parser.error('user aborted and port is not given')
|
||||
else:
|
||||
if not args.port:
|
||||
parser.error('port is not given')
|
||||
try:
|
||||
serial_instance = serial.serial_for_url(
|
||||
args.port,
|
||||
args.baudrate,
|
||||
parity=args.parity,
|
||||
rtscts=args.rtscts,
|
||||
xonxoff=args.xonxoff,
|
||||
do_not_open=True)
|
||||
|
||||
if not hasattr(serial_instance, 'cancel_read'):
|
||||
# enable timeout for alive flag polling if cancel_read is not available
|
||||
serial_instance.timeout = 1
|
||||
|
||||
if args.dtr is not None:
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
|
||||
serial_instance.dtr = args.dtr
|
||||
if args.rts is not None:
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
|
||||
serial_instance.rts = args.rts
|
||||
|
||||
serial_instance.open()
|
||||
except serial.SerialException as e:
|
||||
sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
|
||||
if args.develop:
|
||||
raise
|
||||
if not args.ask:
|
||||
sys.exit(1)
|
||||
else:
|
||||
args.port = '-'
|
||||
else:
|
||||
break
|
||||
|
||||
miniterm = Miniterm(
|
||||
serial_instance,
|
||||
echo=args.echo,
|
||||
eol=args.eol.lower(),
|
||||
filters=filters)
|
||||
miniterm.exit_character = unichr(args.exit_char)
|
||||
miniterm.menu_character = unichr(args.menu_char)
|
||||
miniterm.raw = args.raw
|
||||
miniterm.set_rx_encoding(args.serial_port_encoding)
|
||||
miniterm.set_tx_encoding(args.serial_port_encoding)
|
||||
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
|
||||
p=miniterm.serial))
|
||||
sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
|
||||
key_description(miniterm.exit_character),
|
||||
key_description(miniterm.menu_character),
|
||||
key_description(miniterm.menu_character),
|
||||
key_description('\x08')))
|
||||
|
||||
miniterm.start()
|
||||
try:
|
||||
miniterm.join(True)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
if not args.quiet:
|
||||
sys.stderr.write("\n--- exit ---\n")
|
||||
miniterm.join()
|
||||
miniterm.close()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pip._internal import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pip._internal import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pip._internal import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,3 +0,0 @@
|
||||
UNKNOWN
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,25 +0,0 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: cycler
|
||||
Version: 0.10.0
|
||||
Summary: Composable style cycles
|
||||
Home-page: http://github.com/matplotlib/cycler
|
||||
Author: Thomas A Caswell
|
||||
Author-email: matplotlib-users@python.org
|
||||
License: BSD
|
||||
Keywords: cycle kwargs
|
||||
Platform: Cross platform (Linux
|
||||
Platform: Mac OSX
|
||||
Platform: Windows)
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Requires-Dist: six
|
||||
|
||||
UNKNOWN
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
__pycache__/cycler.cpython-36.pyc,,
|
||||
cycler-0.10.0.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10
|
||||
cycler-0.10.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
cycler-0.10.0.dist-info/METADATA,sha256=aWX1pyo7D2hSDNZ2Q6Zl7DxhUQdpyu1O5uNABnvz000,722
|
||||
cycler-0.10.0.dist-info/RECORD,,
|
||||
cycler-0.10.0.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
|
||||
cycler-0.10.0.dist-info/metadata.json,sha256=CCBpg-KQU-VRL1unJcHPWKQeQbB84G0j7-BeCj7YUbU,875
|
||||
cycler-0.10.0.dist-info/top_level.txt,sha256=D8BVVDdAAelLb2FOEz7lDpc6-AL21ylKPrMhtG6yzyE,7
|
||||
cycler.py,sha256=ed3G39unvVEBrBZVDwnE0FFroRNsOLkbJ_TwIT5CjCU,15959
|
||||
@@ -1,6 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.29.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"classifiers": ["Development Status :: 4 - Beta", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "matplotlib-users@python.org", "name": "Thomas A Caswell", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/matplotlib/cycler"}}}, "extras": [], "generator": "bdist_wheel (0.29.0)", "keywords": ["cycle", "kwargs"], "license": "BSD", "metadata_version": "2.0", "name": "cycler", "platform": "Cross platform (Linux", "run_requires": [{"requires": ["six"]}], "summary": "Composable style cycles", "version": "0.10.0"}
|
||||
@@ -1 +0,0 @@
|
||||
cycler
|
||||
@@ -1,558 +0,0 @@
|
||||
"""
|
||||
Cycler
|
||||
======
|
||||
|
||||
Cycling through combinations of values, producing dictionaries.
|
||||
|
||||
You can add cyclers::
|
||||
|
||||
from cycler import cycler
|
||||
cc = (cycler(color=list('rgb')) +
|
||||
cycler(linestyle=['-', '--', '-.']))
|
||||
for d in cc:
|
||||
print(d)
|
||||
|
||||
Results in::
|
||||
|
||||
{'color': 'r', 'linestyle': '-'}
|
||||
{'color': 'g', 'linestyle': '--'}
|
||||
{'color': 'b', 'linestyle': '-.'}
|
||||
|
||||
|
||||
You can multiply cyclers::
|
||||
|
||||
from cycler import cycler
|
||||
cc = (cycler(color=list('rgb')) *
|
||||
cycler(linestyle=['-', '--', '-.']))
|
||||
for d in cc:
|
||||
print(d)
|
||||
|
||||
Results in::
|
||||
|
||||
{'color': 'r', 'linestyle': '-'}
|
||||
{'color': 'r', 'linestyle': '--'}
|
||||
{'color': 'r', 'linestyle': '-.'}
|
||||
{'color': 'g', 'linestyle': '-'}
|
||||
{'color': 'g', 'linestyle': '--'}
|
||||
{'color': 'g', 'linestyle': '-.'}
|
||||
{'color': 'b', 'linestyle': '-'}
|
||||
{'color': 'b', 'linestyle': '--'}
|
||||
{'color': 'b', 'linestyle': '-.'}
|
||||
"""
|
||||
|
||||
from __future__ import (absolute_import, division, print_function,
|
||||
unicode_literals)
|
||||
|
||||
import six
|
||||
from itertools import product, cycle
|
||||
from six.moves import zip, reduce
|
||||
from operator import mul, add
|
||||
import copy
|
||||
|
||||
__version__ = '0.10.0'
|
||||
|
||||
|
||||
def _process_keys(left, right):
|
||||
"""
|
||||
Helper function to compose cycler keys
|
||||
|
||||
Parameters
|
||||
----------
|
||||
left, right : iterable of dictionaries or None
|
||||
The cyclers to be composed
|
||||
Returns
|
||||
-------
|
||||
keys : set
|
||||
The keys in the composition of the two cyclers
|
||||
"""
|
||||
l_peek = next(iter(left)) if left is not None else {}
|
||||
r_peek = next(iter(right)) if right is not None else {}
|
||||
l_key = set(l_peek.keys())
|
||||
r_key = set(r_peek.keys())
|
||||
if l_key & r_key:
|
||||
raise ValueError("Can not compose overlapping cycles")
|
||||
return l_key | r_key
|
||||
|
||||
|
||||
class Cycler(object):
|
||||
"""
|
||||
Composable cycles
|
||||
|
||||
This class has compositions methods:
|
||||
|
||||
``+``
|
||||
for 'inner' products (zip)
|
||||
|
||||
``+=``
|
||||
in-place ``+``
|
||||
|
||||
``*``
|
||||
for outer products (itertools.product) and integer multiplication
|
||||
|
||||
``*=``
|
||||
in-place ``*``
|
||||
|
||||
and supports basic slicing via ``[]``
|
||||
|
||||
Parameters
|
||||
----------
|
||||
left : Cycler or None
|
||||
The 'left' cycler
|
||||
|
||||
right : Cycler or None
|
||||
The 'right' cycler
|
||||
|
||||
op : func or None
|
||||
Function which composes the 'left' and 'right' cyclers.
|
||||
|
||||
"""
|
||||
def __call__(self):
|
||||
return cycle(self)
|
||||
|
||||
def __init__(self, left, right=None, op=None):
|
||||
"""Semi-private init
|
||||
|
||||
Do not use this directly, use `cycler` function instead.
|
||||
"""
|
||||
if isinstance(left, Cycler):
|
||||
self._left = Cycler(left._left, left._right, left._op)
|
||||
elif left is not None:
|
||||
# Need to copy the dictionary or else that will be a residual
|
||||
# mutable that could lead to strange errors
|
||||
self._left = [copy.copy(v) for v in left]
|
||||
else:
|
||||
self._left = None
|
||||
|
||||
if isinstance(right, Cycler):
|
||||
self._right = Cycler(right._left, right._right, right._op)
|
||||
elif right is not None:
|
||||
# Need to copy the dictionary or else that will be a residual
|
||||
# mutable that could lead to strange errors
|
||||
self._right = [copy.copy(v) for v in right]
|
||||
else:
|
||||
self._right = None
|
||||
|
||||
self._keys = _process_keys(self._left, self._right)
|
||||
self._op = op
|
||||
|
||||
@property
|
||||
def keys(self):
|
||||
"""
|
||||
The keys this Cycler knows about
|
||||
"""
|
||||
return set(self._keys)
|
||||
|
||||
def change_key(self, old, new):
|
||||
"""
|
||||
Change a key in this cycler to a new name.
|
||||
Modification is performed in-place.
|
||||
|
||||
Does nothing if the old key is the same as the new key.
|
||||
Raises a ValueError if the new key is already a key.
|
||||
Raises a KeyError if the old key isn't a key.
|
||||
|
||||
"""
|
||||
if old == new:
|
||||
return
|
||||
if new in self._keys:
|
||||
raise ValueError("Can't replace %s with %s, %s is already a key" %
|
||||
(old, new, new))
|
||||
if old not in self._keys:
|
||||
raise KeyError("Can't replace %s with %s, %s is not a key" %
|
||||
(old, new, old))
|
||||
|
||||
self._keys.remove(old)
|
||||
self._keys.add(new)
|
||||
|
||||
if self._right is not None and old in self._right.keys:
|
||||
self._right.change_key(old, new)
|
||||
|
||||
# self._left should always be non-None
|
||||
# if self._keys is non-empty.
|
||||
elif isinstance(self._left, Cycler):
|
||||
self._left.change_key(old, new)
|
||||
else:
|
||||
# It should be completely safe at this point to
|
||||
# assume that the old key can be found in each
|
||||
# iteration.
|
||||
self._left = [{new: entry[old]} for entry in self._left]
|
||||
|
||||
def _compose(self):
|
||||
"""
|
||||
Compose the 'left' and 'right' components of this cycle
|
||||
with the proper operation (zip or product as of now)
|
||||
"""
|
||||
for a, b in self._op(self._left, self._right):
|
||||
out = dict()
|
||||
out.update(a)
|
||||
out.update(b)
|
||||
yield out
|
||||
|
||||
@classmethod
|
||||
def _from_iter(cls, label, itr):
|
||||
"""
|
||||
Class method to create 'base' Cycler objects
|
||||
that do not have a 'right' or 'op' and for which
|
||||
the 'left' object is not another Cycler.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label : str
|
||||
The property key.
|
||||
|
||||
itr : iterable
|
||||
Finite length iterable of the property values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cycler : Cycler
|
||||
New 'base' `Cycler`
|
||||
"""
|
||||
ret = cls(None)
|
||||
ret._left = list({label: v} for v in itr)
|
||||
ret._keys = set([label])
|
||||
return ret
|
||||
|
||||
def __getitem__(self, key):
|
||||
# TODO : maybe add numpy style fancy slicing
|
||||
if isinstance(key, slice):
|
||||
trans = self.by_key()
|
||||
return reduce(add, (_cycler(k, v[key])
|
||||
for k, v in six.iteritems(trans)))
|
||||
else:
|
||||
raise ValueError("Can only use slices with Cycler.__getitem__")
|
||||
|
||||
def __iter__(self):
|
||||
if self._right is None:
|
||||
return iter(dict(l) for l in self._left)
|
||||
|
||||
return self._compose()
|
||||
|
||||
def __add__(self, other):
|
||||
"""
|
||||
Pair-wise combine two equal length cycles (zip)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : Cycler
|
||||
The second Cycler
|
||||
"""
|
||||
if len(self) != len(other):
|
||||
raise ValueError("Can only add equal length cycles, "
|
||||
"not {0} and {1}".format(len(self), len(other)))
|
||||
return Cycler(self, other, zip)
|
||||
|
||||
def __mul__(self, other):
|
||||
"""
|
||||
Outer product of two cycles (`itertools.product`) or integer
|
||||
multiplication.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : Cycler or int
|
||||
The second Cycler or integer
|
||||
"""
|
||||
if isinstance(other, Cycler):
|
||||
return Cycler(self, other, product)
|
||||
elif isinstance(other, int):
|
||||
trans = self.by_key()
|
||||
return reduce(add, (_cycler(k, v*other)
|
||||
for k, v in six.iteritems(trans)))
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __rmul__(self, other):
|
||||
return self * other
|
||||
|
||||
def __len__(self):
|
||||
op_dict = {zip: min, product: mul}
|
||||
if self._right is None:
|
||||
return len(self._left)
|
||||
l_len = len(self._left)
|
||||
r_len = len(self._right)
|
||||
return op_dict[self._op](l_len, r_len)
|
||||
|
||||
def __iadd__(self, other):
|
||||
"""
|
||||
In-place pair-wise combine two equal length cycles (zip)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : Cycler
|
||||
The second Cycler
|
||||
"""
|
||||
if not isinstance(other, Cycler):
|
||||
raise TypeError("Cannot += with a non-Cycler object")
|
||||
# True shallow copy of self is fine since this is in-place
|
||||
old_self = copy.copy(self)
|
||||
self._keys = _process_keys(old_self, other)
|
||||
self._left = old_self
|
||||
self._op = zip
|
||||
self._right = Cycler(other._left, other._right, other._op)
|
||||
return self
|
||||
|
||||
def __imul__(self, other):
|
||||
"""
|
||||
In-place outer product of two cycles (`itertools.product`)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : Cycler
|
||||
The second Cycler
|
||||
"""
|
||||
if not isinstance(other, Cycler):
|
||||
raise TypeError("Cannot *= with a non-Cycler object")
|
||||
# True shallow copy of self is fine since this is in-place
|
||||
old_self = copy.copy(self)
|
||||
self._keys = _process_keys(old_self, other)
|
||||
self._left = old_self
|
||||
self._op = product
|
||||
self._right = Cycler(other._left, other._right, other._op)
|
||||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Check equality
|
||||
"""
|
||||
if len(self) != len(other):
|
||||
return False
|
||||
if self.keys ^ other.keys:
|
||||
return False
|
||||
|
||||
return all(a == b for a, b in zip(self, other))
|
||||
|
||||
def __repr__(self):
|
||||
op_map = {zip: '+', product: '*'}
|
||||
if self._right is None:
|
||||
lab = self.keys.pop()
|
||||
itr = list(v[lab] for v in self)
|
||||
return "cycler({lab!r}, {itr!r})".format(lab=lab, itr=itr)
|
||||
else:
|
||||
op = op_map.get(self._op, '?')
|
||||
msg = "({left!r} {op} {right!r})"
|
||||
return msg.format(left=self._left, op=op, right=self._right)
|
||||
|
||||
def _repr_html_(self):
|
||||
# an table showing the value of each key through a full cycle
|
||||
output = "<table>"
|
||||
sorted_keys = sorted(self.keys, key=repr)
|
||||
for key in sorted_keys:
|
||||
output += "<th>{key!r}</th>".format(key=key)
|
||||
for d in iter(self):
|
||||
output += "<tr>"
|
||||
for k in sorted_keys:
|
||||
output += "<td>{val!r}</td>".format(val=d[k])
|
||||
output += "</tr>"
|
||||
output += "</table>"
|
||||
return output
|
||||
|
||||
def by_key(self):
|
||||
"""Values by key
|
||||
|
||||
This returns the transposed values of the cycler. Iterating
|
||||
over a `Cycler` yields dicts with a single value for each key,
|
||||
this method returns a `dict` of `list` which are the values
|
||||
for the given key.
|
||||
|
||||
The returned value can be used to create an equivalent `Cycler`
|
||||
using only `+`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
transpose : dict
|
||||
dict of lists of the values for each key.
|
||||
"""
|
||||
|
||||
# TODO : sort out if this is a bottle neck, if there is a better way
|
||||
# and if we care.
|
||||
|
||||
keys = self.keys
|
||||
# change this to dict comprehension when drop 2.6
|
||||
out = dict((k, list()) for k in keys)
|
||||
|
||||
for d in self:
|
||||
for k in keys:
|
||||
out[k].append(d[k])
|
||||
return out
|
||||
|
||||
# for back compatibility
|
||||
_transpose = by_key
|
||||
|
||||
def simplify(self):
|
||||
"""Simplify the Cycler
|
||||
|
||||
Returned as a composition using only sums (no multiplications)
|
||||
|
||||
Returns
|
||||
-------
|
||||
simple : Cycler
|
||||
An equivalent cycler using only summation"""
|
||||
# TODO: sort out if it is worth the effort to make sure this is
|
||||
# balanced. Currently it is is
|
||||
# (((a + b) + c) + d) vs
|
||||
# ((a + b) + (c + d))
|
||||
# I would believe that there is some performance implications
|
||||
|
||||
trans = self.by_key()
|
||||
return reduce(add, (_cycler(k, v) for k, v in six.iteritems(trans)))
|
||||
|
||||
def concat(self, other):
|
||||
"""Concatenate this cycler and an other.
|
||||
|
||||
The keys must match exactly.
|
||||
|
||||
This returns a single Cycler which is equivalent to
|
||||
`itertools.chain(self, other)`
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
>>> num = cycler('a', range(3))
|
||||
>>> let = cycler('a', 'abc')
|
||||
>>> num.concat(let)
|
||||
cycler('a', [0, 1, 2, 'a', 'b', 'c'])
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : `Cycler`
|
||||
The `Cycler` to concatenate to this one.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret : `Cycler`
|
||||
The concatenated `Cycler`
|
||||
"""
|
||||
return concat(self, other)
|
||||
|
||||
|
||||
def concat(left, right):
|
||||
"""Concatenate two cyclers.
|
||||
|
||||
The keys must match exactly.
|
||||
|
||||
This returns a single Cycler which is equivalent to
|
||||
`itertools.chain(left, right)`
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
>>> num = cycler('a', range(3))
|
||||
>>> let = cycler('a', 'abc')
|
||||
>>> num.concat(let)
|
||||
cycler('a', [0, 1, 2, 'a', 'b', 'c'])
|
||||
|
||||
Parameters
|
||||
----------
|
||||
left, right : `Cycler`
|
||||
The two `Cycler` instances to concatenate
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret : `Cycler`
|
||||
The concatenated `Cycler`
|
||||
"""
|
||||
if left.keys != right.keys:
|
||||
msg = '\n\t'.join(["Keys do not match:",
|
||||
"Intersection: {both!r}",
|
||||
"Disjoint: {just_one!r}"]).format(
|
||||
both=left.keys & right.keys,
|
||||
just_one=left.keys ^ right.keys)
|
||||
|
||||
raise ValueError(msg)
|
||||
|
||||
_l = left.by_key()
|
||||
_r = right.by_key()
|
||||
return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys))
|
||||
|
||||
|
||||
def cycler(*args, **kwargs):
|
||||
"""
|
||||
Create a new `Cycler` object from a single positional argument,
|
||||
a pair of positional arguments, or the combination of keyword arguments.
|
||||
|
||||
cycler(arg)
|
||||
cycler(label1=itr1[, label2=iter2[, ...]])
|
||||
cycler(label, itr)
|
||||
|
||||
Form 1 simply copies a given `Cycler` object.
|
||||
|
||||
Form 2 composes a `Cycler` as an inner product of the
|
||||
pairs of keyword arguments. In other words, all of the
|
||||
iterables are cycled simultaneously, as if through zip().
|
||||
|
||||
Form 3 creates a `Cycler` from a label and an iterable.
|
||||
This is useful for when the label cannot be a keyword argument
|
||||
(e.g., an integer or a name that has a space in it).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arg : Cycler
|
||||
Copy constructor for Cycler (does a shallow copy of iterables).
|
||||
|
||||
label : name
|
||||
The property key. In the 2-arg form of the function,
|
||||
the label can be any hashable object. In the keyword argument
|
||||
form of the function, it must be a valid python identifier.
|
||||
|
||||
itr : iterable
|
||||
Finite length iterable of the property values.
|
||||
Can be a single-property `Cycler` that would
|
||||
be like a key change, but as a shallow copy.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cycler : Cycler
|
||||
New `Cycler` for the given property
|
||||
|
||||
"""
|
||||
if args and kwargs:
|
||||
raise TypeError("cyl() can only accept positional OR keyword "
|
||||
"arguments -- not both.")
|
||||
|
||||
if len(args) == 1:
|
||||
if not isinstance(args[0], Cycler):
|
||||
raise TypeError("If only one positional argument given, it must "
|
||||
" be a Cycler instance.")
|
||||
return Cycler(args[0])
|
||||
elif len(args) == 2:
|
||||
return _cycler(*args)
|
||||
elif len(args) > 2:
|
||||
raise TypeError("Only a single Cycler can be accepted as the lone "
|
||||
"positional argument. Use keyword arguments instead.")
|
||||
|
||||
if kwargs:
|
||||
return reduce(add, (_cycler(k, v) for k, v in six.iteritems(kwargs)))
|
||||
|
||||
raise TypeError("Must have at least a positional OR keyword arguments")
|
||||
|
||||
|
||||
def _cycler(label, itr):
|
||||
"""
|
||||
Create a new `Cycler` object from a property name and
|
||||
iterable of values.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label : hashable
|
||||
The property key.
|
||||
|
||||
itr : iterable
|
||||
Finite length iterable of the property values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cycler : Cycler
|
||||
New `Cycler` for the given property
|
||||
"""
|
||||
if isinstance(itr, Cycler):
|
||||
keys = itr.keys
|
||||
if len(keys) != 1:
|
||||
msg = "Can not create Cycler from a multi-property Cycler"
|
||||
raise ValueError(msg)
|
||||
|
||||
lab = keys.pop()
|
||||
# Doesn't need to be a new list because
|
||||
# _from_iter() will be creating that new list anyway.
|
||||
itr = (v[lab] for v in itr)
|
||||
|
||||
return Cycler._from_iter(label, itr)
|
||||
@@ -1,8 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
try:
|
||||
from ._version import version as __version__
|
||||
except ImportError:
|
||||
__version__ = 'unknown'
|
||||
|
||||
__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz',
|
||||
'utils', 'zoneinfo']
|
||||
@@ -1,43 +0,0 @@
|
||||
"""
|
||||
Common code used in multiple modules.
|
||||
"""
|
||||
|
||||
|
||||
class weekday(object):
|
||||
__slots__ = ["weekday", "n"]
|
||||
|
||||
def __init__(self, weekday, n=None):
|
||||
self.weekday = weekday
|
||||
self.n = n
|
||||
|
||||
def __call__(self, n):
|
||||
if n == self.n:
|
||||
return self
|
||||
else:
|
||||
return self.__class__(self.weekday, n)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
if self.weekday != other.weekday or self.n != other.n:
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __hash__(self):
|
||||
return hash((
|
||||
self.weekday,
|
||||
self.n,
|
||||
))
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __repr__(self):
|
||||
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
||||
if not self.n:
|
||||
return s
|
||||
else:
|
||||
return "%s(%+d)" % (s, self.n)
|
||||
|
||||
# vim:ts=4:sw=4:et
|
||||
@@ -1,4 +0,0 @@
|
||||
# coding: utf-8
|
||||
# file generated by setuptools_scm
|
||||
# don't change, don't track in version control
|
||||
version = '2.7.5'
|
||||
@@ -1,89 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module offers a generic easter computing method for any given year, using
|
||||
Western, Orthodox or Julian algorithms.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
|
||||
|
||||
EASTER_JULIAN = 1
|
||||
EASTER_ORTHODOX = 2
|
||||
EASTER_WESTERN = 3
|
||||
|
||||
|
||||
def easter(year, method=EASTER_WESTERN):
|
||||
"""
|
||||
This method was ported from the work done by GM Arts,
|
||||
on top of the algorithm by Claus Tondering, which was
|
||||
based in part on the algorithm of Ouding (1940), as
|
||||
quoted in "Explanatory Supplement to the Astronomical
|
||||
Almanac", P. Kenneth Seidelmann, editor.
|
||||
|
||||
This algorithm implements three different easter
|
||||
calculation methods:
|
||||
|
||||
1 - Original calculation in Julian calendar, valid in
|
||||
dates after 326 AD
|
||||
2 - Original method, with date converted to Gregorian
|
||||
calendar, valid in years 1583 to 4099
|
||||
3 - Revised method, in Gregorian calendar, valid in
|
||||
years 1583 to 4099 as well
|
||||
|
||||
These methods are represented by the constants:
|
||||
|
||||
* ``EASTER_JULIAN = 1``
|
||||
* ``EASTER_ORTHODOX = 2``
|
||||
* ``EASTER_WESTERN = 3``
|
||||
|
||||
The default method is method 3.
|
||||
|
||||
More about the algorithm may be found at:
|
||||
|
||||
`GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_
|
||||
|
||||
and
|
||||
|
||||
`The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_
|
||||
|
||||
"""
|
||||
|
||||
if not (1 <= method <= 3):
|
||||
raise ValueError("invalid method")
|
||||
|
||||
# g - Golden year - 1
|
||||
# c - Century
|
||||
# h - (23 - Epact) mod 30
|
||||
# i - Number of days from March 21 to Paschal Full Moon
|
||||
# j - Weekday for PFM (0=Sunday, etc)
|
||||
# p - Number of days from March 21 to Sunday on or before PFM
|
||||
# (-6 to 28 methods 1 & 3, to 56 for method 2)
|
||||
# e - Extra days to add for method 2 (converting Julian
|
||||
# date to Gregorian date)
|
||||
|
||||
y = year
|
||||
g = y % 19
|
||||
e = 0
|
||||
if method < 3:
|
||||
# Old method
|
||||
i = (19*g + 15) % 30
|
||||
j = (y + y//4 + i) % 7
|
||||
if method == 2:
|
||||
# Extra dates to convert Julian to Gregorian date
|
||||
e = 10
|
||||
if y > 1600:
|
||||
e = e + y//100 - 16 - (y//100 - 16)//4
|
||||
else:
|
||||
# New method
|
||||
c = y//100
|
||||
h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30
|
||||
i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11))
|
||||
j = (y + y//4 + i + 2 - c + c//4) % 7
|
||||
|
||||
# p can be from -6 to 56 corresponding to dates 22 March to 23 May
|
||||
# (later dates apply to method 2, although 23 May never actually occurs)
|
||||
p = i - j + e
|
||||
d = 1 + (p + 27 + (p + 6)//40) % 31
|
||||
m = 3 + (p + 26)//30
|
||||
return datetime.date(int(y), int(m), int(d))
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from ._parser import parse, parser, parserinfo
|
||||
from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
|
||||
from ._parser import UnknownTimezoneWarning
|
||||
|
||||
from ._parser import __doc__
|
||||
|
||||
from .isoparser import isoparser, isoparse
|
||||
|
||||
__all__ = ['parse', 'parser', 'parserinfo',
|
||||
'isoparse', 'isoparser',
|
||||
'UnknownTimezoneWarning']
|
||||
|
||||
|
||||
###
|
||||
# Deprecate portions of the private interface so that downstream code that
|
||||
# is improperly relying on it is given *some* notice.
|
||||
|
||||
|
||||
def __deprecated_private_func(f):
|
||||
from functools import wraps
|
||||
import warnings
|
||||
|
||||
msg = ('{name} is a private function and may break without warning, '
|
||||
'it will be moved and or renamed in future versions.')
|
||||
msg = msg.format(name=f.__name__)
|
||||
|
||||
@wraps(f)
|
||||
def deprecated_func(*args, **kwargs):
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return deprecated_func
|
||||
|
||||
def __deprecate_private_class(c):
|
||||
import warnings
|
||||
|
||||
msg = ('{name} is a private class and may break without warning, '
|
||||
'it will be moved and or renamed in future versions.')
|
||||
msg = msg.format(name=c.__name__)
|
||||
|
||||
class private_class(c):
|
||||
__doc__ = c.__doc__
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
super(private_class, self).__init__(*args, **kwargs)
|
||||
|
||||
private_class.__name__ = c.__name__
|
||||
|
||||
return private_class
|
||||
|
||||
|
||||
from ._parser import _timelex, _resultbase
|
||||
from ._parser import _tzparser, _parsetz
|
||||
|
||||
_timelex = __deprecate_private_class(_timelex)
|
||||
_tzparser = __deprecate_private_class(_tzparser)
|
||||
_resultbase = __deprecate_private_class(_resultbase)
|
||||
_parsetz = __deprecated_private_func(_parsetz)
|
||||
@@ -1,406 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module offers a parser for ISO-8601 strings
|
||||
|
||||
It is intended to support all valid date, time and datetime formats per the
|
||||
ISO-8601 specification.
|
||||
|
||||
..versionadded:: 2.7.0
|
||||
"""
|
||||
from datetime import datetime, timedelta, time, date
|
||||
import calendar
|
||||
from dateutil import tz
|
||||
|
||||
from functools import wraps
|
||||
|
||||
import re
|
||||
import six
|
||||
|
||||
__all__ = ["isoparse", "isoparser"]
|
||||
|
||||
|
||||
def _takes_ascii(f):
|
||||
@wraps(f)
|
||||
def func(self, str_in, *args, **kwargs):
|
||||
# If it's a stream, read the whole thing
|
||||
str_in = getattr(str_in, 'read', lambda: str_in)()
|
||||
|
||||
# If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII
|
||||
if isinstance(str_in, six.text_type):
|
||||
# ASCII is the same in UTF-8
|
||||
try:
|
||||
str_in = str_in.encode('ascii')
|
||||
except UnicodeEncodeError as e:
|
||||
msg = 'ISO-8601 strings should contain only ASCII characters'
|
||||
six.raise_from(ValueError(msg), e)
|
||||
|
||||
return f(self, str_in, *args, **kwargs)
|
||||
|
||||
return func
|
||||
|
||||
|
||||
class isoparser(object):
|
||||
def __init__(self, sep=None):
|
||||
"""
|
||||
:param sep:
|
||||
A single character that separates date and time portions. If
|
||||
``None``, the parser will accept any single character.
|
||||
For strict ISO-8601 adherence, pass ``'T'``.
|
||||
"""
|
||||
if sep is not None:
|
||||
if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'):
|
||||
raise ValueError('Separator must be a single, non-numeric ' +
|
||||
'ASCII character')
|
||||
|
||||
sep = sep.encode('ascii')
|
||||
|
||||
self._sep = sep
|
||||
|
||||
@_takes_ascii
|
||||
def isoparse(self, dt_str):
|
||||
"""
|
||||
Parse an ISO-8601 datetime string into a :class:`datetime.datetime`.
|
||||
|
||||
An ISO-8601 datetime string consists of a date portion, followed
|
||||
optionally by a time portion - the date and time portions are separated
|
||||
by a single character separator, which is ``T`` in the official
|
||||
standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be
|
||||
combined with a time portion.
|
||||
|
||||
Supported date formats are:
|
||||
|
||||
Common:
|
||||
|
||||
- ``YYYY``
|
||||
- ``YYYY-MM`` or ``YYYYMM``
|
||||
- ``YYYY-MM-DD`` or ``YYYYMMDD``
|
||||
|
||||
Uncommon:
|
||||
|
||||
- ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0)
|
||||
- ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day
|
||||
|
||||
The ISO week and day numbering follows the same logic as
|
||||
:func:`datetime.date.isocalendar`.
|
||||
|
||||
Supported time formats are:
|
||||
|
||||
- ``hh``
|
||||
- ``hh:mm`` or ``hhmm``
|
||||
- ``hh:mm:ss`` or ``hhmmss``
|
||||
- ``hh:mm:ss.sss`` or ``hh:mm:ss.ssssss`` (3-6 sub-second digits)
|
||||
|
||||
Midnight is a special case for `hh`, as the standard supports both
|
||||
00:00 and 24:00 as a representation.
|
||||
|
||||
.. caution::
|
||||
|
||||
Support for fractional components other than seconds is part of the
|
||||
ISO-8601 standard, but is not currently implemented in this parser.
|
||||
|
||||
Supported time zone offset formats are:
|
||||
|
||||
- `Z` (UTC)
|
||||
- `±HH:MM`
|
||||
- `±HHMM`
|
||||
- `±HH`
|
||||
|
||||
Offsets will be represented as :class:`dateutil.tz.tzoffset` objects,
|
||||
with the exception of UTC, which will be represented as
|
||||
:class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such
|
||||
as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`.
|
||||
|
||||
:param dt_str:
|
||||
A string or stream containing only an ISO-8601 datetime string
|
||||
|
||||
:return:
|
||||
Returns a :class:`datetime.datetime` representing the string.
|
||||
Unspecified components default to their lowest value.
|
||||
|
||||
.. warning::
|
||||
|
||||
As of version 2.7.0, the strictness of the parser should not be
|
||||
considered a stable part of the contract. Any valid ISO-8601 string
|
||||
that parses correctly with the default settings will continue to
|
||||
parse correctly in future versions, but invalid strings that
|
||||
currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not
|
||||
guaranteed to continue failing in future versions if they encode
|
||||
a valid date.
|
||||
|
||||
.. versionadded:: 2.7.0
|
||||
"""
|
||||
components, pos = self._parse_isodate(dt_str)
|
||||
|
||||
if len(dt_str) > pos:
|
||||
if self._sep is None or dt_str[pos:pos + 1] == self._sep:
|
||||
components += self._parse_isotime(dt_str[pos + 1:])
|
||||
else:
|
||||
raise ValueError('String contains unknown ISO components')
|
||||
|
||||
return datetime(*components)
|
||||
|
||||
@_takes_ascii
|
||||
def parse_isodate(self, datestr):
|
||||
"""
|
||||
Parse the date portion of an ISO string.
|
||||
|
||||
:param datestr:
|
||||
The string portion of an ISO string, without a separator
|
||||
|
||||
:return:
|
||||
Returns a :class:`datetime.date` object
|
||||
"""
|
||||
components, pos = self._parse_isodate(datestr)
|
||||
if pos < len(datestr):
|
||||
raise ValueError('String contains unknown ISO ' +
|
||||
'components: {}'.format(datestr))
|
||||
return date(*components)
|
||||
|
||||
@_takes_ascii
|
||||
def parse_isotime(self, timestr):
|
||||
"""
|
||||
Parse the time portion of an ISO string.
|
||||
|
||||
:param timestr:
|
||||
The time portion of an ISO string, without a separator
|
||||
|
||||
:return:
|
||||
Returns a :class:`datetime.time` object
|
||||
"""
|
||||
return time(*self._parse_isotime(timestr))
|
||||
|
||||
@_takes_ascii
|
||||
def parse_tzstr(self, tzstr, zero_as_utc=True):
|
||||
"""
|
||||
Parse a valid ISO time zone string.
|
||||
|
||||
See :func:`isoparser.isoparse` for details on supported formats.
|
||||
|
||||
:param tzstr:
|
||||
A string representing an ISO time zone offset
|
||||
|
||||
:param zero_as_utc:
|
||||
Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones
|
||||
|
||||
:return:
|
||||
Returns :class:`dateutil.tz.tzoffset` for offsets and
|
||||
:class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is
|
||||
specified) offsets equivalent to UTC.
|
||||
"""
|
||||
return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
|
||||
|
||||
# Constants
|
||||
_MICROSECOND_END_REGEX = re.compile(b'[-+Z]+')
|
||||
_DATE_SEP = b'-'
|
||||
_TIME_SEP = b':'
|
||||
_MICRO_SEP = b'.'
|
||||
|
||||
def _parse_isodate(self, dt_str):
|
||||
try:
|
||||
return self._parse_isodate_common(dt_str)
|
||||
except ValueError:
|
||||
return self._parse_isodate_uncommon(dt_str)
|
||||
|
||||
def _parse_isodate_common(self, dt_str):
|
||||
len_str = len(dt_str)
|
||||
components = [1, 1, 1]
|
||||
|
||||
if len_str < 4:
|
||||
raise ValueError('ISO string too short')
|
||||
|
||||
# Year
|
||||
components[0] = int(dt_str[0:4])
|
||||
pos = 4
|
||||
if pos >= len_str:
|
||||
return components, pos
|
||||
|
||||
has_sep = dt_str[pos:pos + 1] == self._DATE_SEP
|
||||
if has_sep:
|
||||
pos += 1
|
||||
|
||||
# Month
|
||||
if len_str - pos < 2:
|
||||
raise ValueError('Invalid common month')
|
||||
|
||||
components[1] = int(dt_str[pos:pos + 2])
|
||||
pos += 2
|
||||
|
||||
if pos >= len_str:
|
||||
if has_sep:
|
||||
return components, pos
|
||||
else:
|
||||
raise ValueError('Invalid ISO format')
|
||||
|
||||
if has_sep:
|
||||
if dt_str[pos:pos + 1] != self._DATE_SEP:
|
||||
raise ValueError('Invalid separator in ISO string')
|
||||
pos += 1
|
||||
|
||||
# Day
|
||||
if len_str - pos < 2:
|
||||
raise ValueError('Invalid common day')
|
||||
components[2] = int(dt_str[pos:pos + 2])
|
||||
return components, pos + 2
|
||||
|
||||
def _parse_isodate_uncommon(self, dt_str):
|
||||
if len(dt_str) < 4:
|
||||
raise ValueError('ISO string too short')
|
||||
|
||||
# All ISO formats start with the year
|
||||
year = int(dt_str[0:4])
|
||||
|
||||
has_sep = dt_str[4:5] == self._DATE_SEP
|
||||
|
||||
pos = 4 + has_sep # Skip '-' if it's there
|
||||
if dt_str[pos:pos + 1] == b'W':
|
||||
# YYYY-?Www-?D?
|
||||
pos += 1
|
||||
weekno = int(dt_str[pos:pos + 2])
|
||||
pos += 2
|
||||
|
||||
dayno = 1
|
||||
if len(dt_str) > pos:
|
||||
if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep:
|
||||
raise ValueError('Inconsistent use of dash separator')
|
||||
|
||||
pos += has_sep
|
||||
|
||||
dayno = int(dt_str[pos:pos + 1])
|
||||
pos += 1
|
||||
|
||||
base_date = self._calculate_weekdate(year, weekno, dayno)
|
||||
else:
|
||||
# YYYYDDD or YYYY-DDD
|
||||
if len(dt_str) - pos < 3:
|
||||
raise ValueError('Invalid ordinal day')
|
||||
|
||||
ordinal_day = int(dt_str[pos:pos + 3])
|
||||
pos += 3
|
||||
|
||||
if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)):
|
||||
raise ValueError('Invalid ordinal day' +
|
||||
' {} for year {}'.format(ordinal_day, year))
|
||||
|
||||
base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1)
|
||||
|
||||
components = [base_date.year, base_date.month, base_date.day]
|
||||
return components, pos
|
||||
|
||||
def _calculate_weekdate(self, year, week, day):
|
||||
"""
|
||||
Calculate the day of corresponding to the ISO year-week-day calendar.
|
||||
|
||||
This function is effectively the inverse of
|
||||
:func:`datetime.date.isocalendar`.
|
||||
|
||||
:param year:
|
||||
The year in the ISO calendar
|
||||
|
||||
:param week:
|
||||
The week in the ISO calendar - range is [1, 53]
|
||||
|
||||
:param day:
|
||||
The day in the ISO calendar - range is [1 (MON), 7 (SUN)]
|
||||
|
||||
:return:
|
||||
Returns a :class:`datetime.date`
|
||||
"""
|
||||
if not 0 < week < 54:
|
||||
raise ValueError('Invalid week: {}'.format(week))
|
||||
|
||||
if not 0 < day < 8: # Range is 1-7
|
||||
raise ValueError('Invalid weekday: {}'.format(day))
|
||||
|
||||
# Get week 1 for the specific year:
|
||||
jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it
|
||||
week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1)
|
||||
|
||||
# Now add the specific number of weeks and days to get what we want
|
||||
week_offset = (week - 1) * 7 + (day - 1)
|
||||
return week_1 + timedelta(days=week_offset)
|
||||
|
||||
def _parse_isotime(self, timestr):
|
||||
len_str = len(timestr)
|
||||
components = [0, 0, 0, 0, None]
|
||||
pos = 0
|
||||
comp = -1
|
||||
|
||||
if len(timestr) < 2:
|
||||
raise ValueError('ISO time too short')
|
||||
|
||||
has_sep = len_str >= 3 and timestr[2:3] == self._TIME_SEP
|
||||
|
||||
while pos < len_str and comp < 5:
|
||||
comp += 1
|
||||
|
||||
if timestr[pos:pos + 1] in b'-+Z':
|
||||
# Detect time zone boundary
|
||||
components[-1] = self._parse_tzstr(timestr[pos:])
|
||||
pos = len_str
|
||||
break
|
||||
|
||||
if comp < 3:
|
||||
# Hour, minute, second
|
||||
components[comp] = int(timestr[pos:pos + 2])
|
||||
pos += 2
|
||||
if (has_sep and pos < len_str and
|
||||
timestr[pos:pos + 1] == self._TIME_SEP):
|
||||
pos += 1
|
||||
|
||||
if comp == 3:
|
||||
# Microsecond
|
||||
if timestr[pos:pos + 1] != self._MICRO_SEP:
|
||||
continue
|
||||
|
||||
pos += 1
|
||||
us_str = self._MICROSECOND_END_REGEX.split(timestr[pos:pos + 6],
|
||||
1)[0]
|
||||
|
||||
components[comp] = int(us_str) * 10**(6 - len(us_str))
|
||||
pos += len(us_str)
|
||||
|
||||
if pos < len_str:
|
||||
raise ValueError('Unused components in ISO string')
|
||||
|
||||
if components[0] == 24:
|
||||
# Standard supports 00:00 and 24:00 as representations of midnight
|
||||
if any(component != 0 for component in components[1:4]):
|
||||
raise ValueError('Hour may only be 24 at 24:00:00.000')
|
||||
components[0] = 0
|
||||
|
||||
return components
|
||||
|
||||
def _parse_tzstr(self, tzstr, zero_as_utc=True):
|
||||
if tzstr == b'Z':
|
||||
return tz.tzutc()
|
||||
|
||||
if len(tzstr) not in {3, 5, 6}:
|
||||
raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters')
|
||||
|
||||
if tzstr[0:1] == b'-':
|
||||
mult = -1
|
||||
elif tzstr[0:1] == b'+':
|
||||
mult = 1
|
||||
else:
|
||||
raise ValueError('Time zone offset requires sign')
|
||||
|
||||
hours = int(tzstr[1:3])
|
||||
if len(tzstr) == 3:
|
||||
minutes = 0
|
||||
else:
|
||||
minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):])
|
||||
|
||||
if zero_as_utc and hours == 0 and minutes == 0:
|
||||
return tz.tzutc()
|
||||
else:
|
||||
if minutes > 59:
|
||||
raise ValueError('Invalid minutes in time zone offset')
|
||||
|
||||
if hours > 23:
|
||||
raise ValueError('Invalid hours in time zone offset')
|
||||
|
||||
return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60)
|
||||
|
||||
|
||||
DEFAULT_ISOPARSER = isoparser()
|
||||
isoparse = DEFAULT_ISOPARSER.isoparse
|
||||
@@ -1,590 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import calendar
|
||||
|
||||
import operator
|
||||
from math import copysign
|
||||
|
||||
from six import integer_types
|
||||
from warnings import warn
|
||||
|
||||
from ._common import weekday
|
||||
|
||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
|
||||
|
||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||
|
||||
|
||||
class relativedelta(object):
|
||||
"""
|
||||
The relativedelta type is based on the specification of the excellent
|
||||
work done by M.-A. Lemburg in his
|
||||
`mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
|
||||
However, notice that this type does *NOT* implement the same algorithm as
|
||||
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
||||
|
||||
There are two different ways to build a relativedelta instance. The
|
||||
first one is passing it two date/datetime classes::
|
||||
|
||||
relativedelta(datetime1, datetime2)
|
||||
|
||||
The second one is passing it any number of the following keyword arguments::
|
||||
|
||||
relativedelta(arg1=x,arg2=y,arg3=z...)
|
||||
|
||||
year, month, day, hour, minute, second, microsecond:
|
||||
Absolute information (argument is singular); adding or subtracting a
|
||||
relativedelta with absolute information does not perform an arithmetic
|
||||
operation, but rather REPLACES the corresponding value in the
|
||||
original datetime with the value(s) in relativedelta.
|
||||
|
||||
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
||||
Relative information, may be negative (argument is plural); adding
|
||||
or subtracting a relativedelta with relative information performs
|
||||
the corresponding aritmetic operation on the original datetime value
|
||||
with the information in the relativedelta.
|
||||
|
||||
weekday:
|
||||
One of the weekday instances (MO, TU, etc). These
|
||||
instances may receive a parameter N, specifying the Nth
|
||||
weekday, which could be positive or negative (like MO(+1)
|
||||
or MO(-2). Not specifying it is the same as specifying
|
||||
+1. You can also use an integer, where 0=MO. Notice that
|
||||
if the calculated date is already Monday, for example,
|
||||
using MO(1) or MO(-1) won't change the day.
|
||||
|
||||
leapdays:
|
||||
Will add given days to the date found, if year is a leap
|
||||
year, and the date found is post 28 of february.
|
||||
|
||||
yearday, nlyearday:
|
||||
Set the yearday or the non-leap year day (jump leap days).
|
||||
These are converted to day/month/leapdays information.
|
||||
|
||||
There are relative and absolute forms of the keyword
|
||||
arguments. The plural is relative, and the singular is
|
||||
absolute. For each argument in the order below, the absolute form
|
||||
is applied first (by setting each attribute to that value) and
|
||||
then the relative form (by adding the value to the attribute).
|
||||
|
||||
The order of attributes considered when this relativedelta is
|
||||
added to a datetime is:
|
||||
|
||||
1. Year
|
||||
2. Month
|
||||
3. Day
|
||||
4. Hours
|
||||
5. Minutes
|
||||
6. Seconds
|
||||
7. Microseconds
|
||||
|
||||
Finally, weekday is applied, using the rule described above.
|
||||
|
||||
For example
|
||||
|
||||
>>> dt = datetime(2018, 4, 9, 13, 37, 0)
|
||||
>>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
|
||||
datetime(2018, 4, 2, 14, 37, 0)
|
||||
|
||||
First, the day is set to 1 (the first of the month), then 25 hours
|
||||
are added, to get to the 2nd day and 14th hour, finally the
|
||||
weekday is applied, but since the 2nd is already a Monday there is
|
||||
no effect.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, dt1=None, dt2=None,
|
||||
years=0, months=0, days=0, leapdays=0, weeks=0,
|
||||
hours=0, minutes=0, seconds=0, microseconds=0,
|
||||
year=None, month=None, day=None, weekday=None,
|
||||
yearday=None, nlyearday=None,
|
||||
hour=None, minute=None, second=None, microsecond=None):
|
||||
|
||||
if dt1 and dt2:
|
||||
# datetime is a subclass of date. So both must be date
|
||||
if not (isinstance(dt1, datetime.date) and
|
||||
isinstance(dt2, datetime.date)):
|
||||
raise TypeError("relativedelta only diffs datetime/date")
|
||||
|
||||
# We allow two dates, or two datetimes, so we coerce them to be
|
||||
# of the same type
|
||||
if (isinstance(dt1, datetime.datetime) !=
|
||||
isinstance(dt2, datetime.datetime)):
|
||||
if not isinstance(dt1, datetime.datetime):
|
||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||
elif not isinstance(dt2, datetime.datetime):
|
||||
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
||||
|
||||
self.years = 0
|
||||
self.months = 0
|
||||
self.days = 0
|
||||
self.leapdays = 0
|
||||
self.hours = 0
|
||||
self.minutes = 0
|
||||
self.seconds = 0
|
||||
self.microseconds = 0
|
||||
self.year = None
|
||||
self.month = None
|
||||
self.day = None
|
||||
self.weekday = None
|
||||
self.hour = None
|
||||
self.minute = None
|
||||
self.second = None
|
||||
self.microsecond = None
|
||||
self._has_time = 0
|
||||
|
||||
# Get year / month delta between the two
|
||||
months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
|
||||
self._set_months(months)
|
||||
|
||||
# Remove the year/month delta so the timedelta is just well-defined
|
||||
# time units (seconds, days and microseconds)
|
||||
dtm = self.__radd__(dt2)
|
||||
|
||||
# If we've overshot our target, make an adjustment
|
||||
if dt1 < dt2:
|
||||
compare = operator.gt
|
||||
increment = 1
|
||||
else:
|
||||
compare = operator.lt
|
||||
increment = -1
|
||||
|
||||
while compare(dt1, dtm):
|
||||
months += increment
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
|
||||
# Get the timedelta between the "months-adjusted" date and dt1
|
||||
delta = dt1 - dtm
|
||||
self.seconds = delta.seconds + delta.days * 86400
|
||||
self.microseconds = delta.microseconds
|
||||
else:
|
||||
# Check for non-integer values in integer-only quantities
|
||||
if any(x is not None and x != int(x) for x in (years, months)):
|
||||
raise ValueError("Non-integer years and months are "
|
||||
"ambiguous and not currently supported.")
|
||||
|
||||
# Relative information
|
||||
self.years = int(years)
|
||||
self.months = int(months)
|
||||
self.days = days + weeks * 7
|
||||
self.leapdays = leapdays
|
||||
self.hours = hours
|
||||
self.minutes = minutes
|
||||
self.seconds = seconds
|
||||
self.microseconds = microseconds
|
||||
|
||||
# Absolute information
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
self.hour = hour
|
||||
self.minute = minute
|
||||
self.second = second
|
||||
self.microsecond = microsecond
|
||||
|
||||
if any(x is not None and int(x) != x
|
||||
for x in (year, month, day, hour,
|
||||
minute, second, microsecond)):
|
||||
# For now we'll deprecate floats - later it'll be an error.
|
||||
warn("Non-integer value passed as absolute information. " +
|
||||
"This is not a well-defined condition and will raise " +
|
||||
"errors in future versions.", DeprecationWarning)
|
||||
|
||||
if isinstance(weekday, integer_types):
|
||||
self.weekday = weekdays[weekday]
|
||||
else:
|
||||
self.weekday = weekday
|
||||
|
||||
yday = 0
|
||||
if nlyearday:
|
||||
yday = nlyearday
|
||||
elif yearday:
|
||||
yday = yearday
|
||||
if yearday > 59:
|
||||
self.leapdays = -1
|
||||
if yday:
|
||||
ydayidx = [31, 59, 90, 120, 151, 181, 212,
|
||||
243, 273, 304, 334, 366]
|
||||
for idx, ydays in enumerate(ydayidx):
|
||||
if yday <= ydays:
|
||||
self.month = idx+1
|
||||
if idx == 0:
|
||||
self.day = yday
|
||||
else:
|
||||
self.day = yday-ydayidx[idx-1]
|
||||
break
|
||||
else:
|
||||
raise ValueError("invalid year day (%d)" % yday)
|
||||
|
||||
self._fix()
|
||||
|
||||
def _fix(self):
|
||||
if abs(self.microseconds) > 999999:
|
||||
s = _sign(self.microseconds)
|
||||
div, mod = divmod(self.microseconds * s, 1000000)
|
||||
self.microseconds = mod * s
|
||||
self.seconds += div * s
|
||||
if abs(self.seconds) > 59:
|
||||
s = _sign(self.seconds)
|
||||
div, mod = divmod(self.seconds * s, 60)
|
||||
self.seconds = mod * s
|
||||
self.minutes += div * s
|
||||
if abs(self.minutes) > 59:
|
||||
s = _sign(self.minutes)
|
||||
div, mod = divmod(self.minutes * s, 60)
|
||||
self.minutes = mod * s
|
||||
self.hours += div * s
|
||||
if abs(self.hours) > 23:
|
||||
s = _sign(self.hours)
|
||||
div, mod = divmod(self.hours * s, 24)
|
||||
self.hours = mod * s
|
||||
self.days += div * s
|
||||
if abs(self.months) > 11:
|
||||
s = _sign(self.months)
|
||||
div, mod = divmod(self.months * s, 12)
|
||||
self.months = mod * s
|
||||
self.years += div * s
|
||||
if (self.hours or self.minutes or self.seconds or self.microseconds
|
||||
or self.hour is not None or self.minute is not None or
|
||||
self.second is not None or self.microsecond is not None):
|
||||
self._has_time = 1
|
||||
else:
|
||||
self._has_time = 0
|
||||
|
||||
@property
|
||||
def weeks(self):
|
||||
return int(self.days / 7.0)
|
||||
|
||||
@weeks.setter
|
||||
def weeks(self, value):
|
||||
self.days = self.days - (self.weeks * 7) + value * 7
|
||||
|
||||
def _set_months(self, months):
|
||||
self.months = months
|
||||
if abs(self.months) > 11:
|
||||
s = _sign(self.months)
|
||||
div, mod = divmod(self.months * s, 12)
|
||||
self.months = mod * s
|
||||
self.years = div * s
|
||||
else:
|
||||
self.years = 0
|
||||
|
||||
def normalized(self):
|
||||
"""
|
||||
Return a version of this object represented entirely using integer
|
||||
values for the relative attributes.
|
||||
|
||||
>>> relativedelta(days=1.5, hours=2).normalized()
|
||||
relativedelta(days=1, hours=14)
|
||||
|
||||
:return:
|
||||
Returns a :class:`dateutil.relativedelta.relativedelta` object.
|
||||
"""
|
||||
# Cascade remainders down (rounding each to roughly nearest microsecond)
|
||||
days = int(self.days)
|
||||
|
||||
hours_f = round(self.hours + 24 * (self.days - days), 11)
|
||||
hours = int(hours_f)
|
||||
|
||||
minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
|
||||
minutes = int(minutes_f)
|
||||
|
||||
seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
|
||||
seconds = int(seconds_f)
|
||||
|
||||
microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))
|
||||
|
||||
# Constructor carries overflow back up with call to _fix()
|
||||
return self.__class__(years=self.years, months=self.months,
|
||||
days=days, hours=hours, minutes=minutes,
|
||||
seconds=seconds, microseconds=microseconds,
|
||||
leapdays=self.leapdays, year=self.year,
|
||||
month=self.month, day=self.day,
|
||||
weekday=self.weekday, hour=self.hour,
|
||||
minute=self.minute, second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, relativedelta):
|
||||
return self.__class__(years=other.years + self.years,
|
||||
months=other.months + self.months,
|
||||
days=other.days + self.days,
|
||||
hours=other.hours + self.hours,
|
||||
minutes=other.minutes + self.minutes,
|
||||
seconds=other.seconds + self.seconds,
|
||||
microseconds=(other.microseconds +
|
||||
self.microseconds),
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=(other.year if other.year is not None
|
||||
else self.year),
|
||||
month=(other.month if other.month is not None
|
||||
else self.month),
|
||||
day=(other.day if other.day is not None
|
||||
else self.day),
|
||||
weekday=(other.weekday if other.weekday is not None
|
||||
else self.weekday),
|
||||
hour=(other.hour if other.hour is not None
|
||||
else self.hour),
|
||||
minute=(other.minute if other.minute is not None
|
||||
else self.minute),
|
||||
second=(other.second if other.second is not None
|
||||
else self.second),
|
||||
microsecond=(other.microsecond if other.microsecond
|
||||
is not None else
|
||||
self.microsecond))
|
||||
if isinstance(other, datetime.timedelta):
|
||||
return self.__class__(years=self.years,
|
||||
months=self.months,
|
||||
days=self.days + other.days,
|
||||
hours=self.hours,
|
||||
minutes=self.minutes,
|
||||
seconds=self.seconds + other.seconds,
|
||||
microseconds=self.microseconds + other.microseconds,
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
if not isinstance(other, datetime.date):
|
||||
return NotImplemented
|
||||
elif self._has_time and not isinstance(other, datetime.datetime):
|
||||
other = datetime.datetime.fromordinal(other.toordinal())
|
||||
year = (self.year or other.year)+self.years
|
||||
month = self.month or other.month
|
||||
if self.months:
|
||||
assert 1 <= abs(self.months) <= 12
|
||||
month += self.months
|
||||
if month > 12:
|
||||
year += 1
|
||||
month -= 12
|
||||
elif month < 1:
|
||||
year -= 1
|
||||
month += 12
|
||||
day = min(calendar.monthrange(year, month)[1],
|
||||
self.day or other.day)
|
||||
repl = {"year": year, "month": month, "day": day}
|
||||
for attr in ["hour", "minute", "second", "microsecond"]:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
repl[attr] = value
|
||||
days = self.days
|
||||
if self.leapdays and month > 2 and calendar.isleap(year):
|
||||
days += self.leapdays
|
||||
ret = (other.replace(**repl)
|
||||
+ datetime.timedelta(days=days,
|
||||
hours=self.hours,
|
||||
minutes=self.minutes,
|
||||
seconds=self.seconds,
|
||||
microseconds=self.microseconds))
|
||||
if self.weekday:
|
||||
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
||||
jumpdays = (abs(nth) - 1) * 7
|
||||
if nth > 0:
|
||||
jumpdays += (7 - ret.weekday() + weekday) % 7
|
||||
else:
|
||||
jumpdays += (ret.weekday() - weekday) % 7
|
||||
jumpdays *= -1
|
||||
ret += datetime.timedelta(days=jumpdays)
|
||||
return ret
|
||||
|
||||
def __radd__(self, other):
|
||||
return self.__add__(other)
|
||||
|
||||
def __rsub__(self, other):
|
||||
return self.__neg__().__radd__(other)
|
||||
|
||||
def __sub__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
return NotImplemented # In case the other object defines __rsub__
|
||||
return self.__class__(years=self.years - other.years,
|
||||
months=self.months - other.months,
|
||||
days=self.days - other.days,
|
||||
hours=self.hours - other.hours,
|
||||
minutes=self.minutes - other.minutes,
|
||||
seconds=self.seconds - other.seconds,
|
||||
microseconds=self.microseconds - other.microseconds,
|
||||
leapdays=self.leapdays or other.leapdays,
|
||||
year=(self.year if self.year is not None
|
||||
else other.year),
|
||||
month=(self.month if self.month is not None else
|
||||
other.month),
|
||||
day=(self.day if self.day is not None else
|
||||
other.day),
|
||||
weekday=(self.weekday if self.weekday is not None else
|
||||
other.weekday),
|
||||
hour=(self.hour if self.hour is not None else
|
||||
other.hour),
|
||||
minute=(self.minute if self.minute is not None else
|
||||
other.minute),
|
||||
second=(self.second if self.second is not None else
|
||||
other.second),
|
||||
microsecond=(self.microsecond if self.microsecond
|
||||
is not None else
|
||||
other.microsecond))
|
||||
|
||||
def __abs__(self):
|
||||
return self.__class__(years=abs(self.years),
|
||||
months=abs(self.months),
|
||||
days=abs(self.days),
|
||||
hours=abs(self.hours),
|
||||
minutes=abs(self.minutes),
|
||||
seconds=abs(self.seconds),
|
||||
microseconds=abs(self.microseconds),
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __neg__(self):
|
||||
return self.__class__(years=-self.years,
|
||||
months=-self.months,
|
||||
days=-self.days,
|
||||
hours=-self.hours,
|
||||
minutes=-self.minutes,
|
||||
seconds=-self.seconds,
|
||||
microseconds=-self.microseconds,
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __bool__(self):
|
||||
return not (not self.years and
|
||||
not self.months and
|
||||
not self.days and
|
||||
not self.hours and
|
||||
not self.minutes and
|
||||
not self.seconds and
|
||||
not self.microseconds and
|
||||
not self.leapdays and
|
||||
self.year is None and
|
||||
self.month is None and
|
||||
self.day is None and
|
||||
self.weekday is None and
|
||||
self.hour is None and
|
||||
self.minute is None and
|
||||
self.second is None and
|
||||
self.microsecond is None)
|
||||
# Compatibility with Python 2.x
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __mul__(self, other):
|
||||
try:
|
||||
f = float(other)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
|
||||
return self.__class__(years=int(self.years * f),
|
||||
months=int(self.months * f),
|
||||
days=int(self.days * f),
|
||||
hours=int(self.hours * f),
|
||||
minutes=int(self.minutes * f),
|
||||
seconds=int(self.seconds * f),
|
||||
microseconds=int(self.microseconds * f),
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
return NotImplemented
|
||||
if self.weekday or other.weekday:
|
||||
if not self.weekday or not other.weekday:
|
||||
return False
|
||||
if self.weekday.weekday != other.weekday.weekday:
|
||||
return False
|
||||
n1, n2 = self.weekday.n, other.weekday.n
|
||||
if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
|
||||
return False
|
||||
return (self.years == other.years and
|
||||
self.months == other.months and
|
||||
self.days == other.days and
|
||||
self.hours == other.hours and
|
||||
self.minutes == other.minutes and
|
||||
self.seconds == other.seconds and
|
||||
self.microseconds == other.microseconds and
|
||||
self.leapdays == other.leapdays and
|
||||
self.year == other.year and
|
||||
self.month == other.month and
|
||||
self.day == other.day and
|
||||
self.hour == other.hour and
|
||||
self.minute == other.minute and
|
||||
self.second == other.second and
|
||||
self.microsecond == other.microsecond)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((
|
||||
self.weekday,
|
||||
self.years,
|
||||
self.months,
|
||||
self.days,
|
||||
self.hours,
|
||||
self.minutes,
|
||||
self.seconds,
|
||||
self.microseconds,
|
||||
self.leapdays,
|
||||
self.year,
|
||||
self.month,
|
||||
self.day,
|
||||
self.hour,
|
||||
self.minute,
|
||||
self.second,
|
||||
self.microsecond,
|
||||
))
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __div__(self, other):
|
||||
try:
|
||||
reciprocal = 1 / float(other)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
|
||||
return self.__mul__(reciprocal)
|
||||
|
||||
__truediv__ = __div__
|
||||
|
||||
def __repr__(self):
|
||||
l = []
|
||||
for attr in ["years", "months", "days", "leapdays",
|
||||
"hours", "minutes", "seconds", "microseconds"]:
|
||||
value = getattr(self, attr)
|
||||
if value:
|
||||
l.append("{attr}={value:+g}".format(attr=attr, value=value))
|
||||
for attr in ["year", "month", "day", "weekday",
|
||||
"hour", "minute", "second", "microsecond"]:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("{attr}={value}".format(attr=attr, value=repr(value)))
|
||||
return "{classname}({attrs})".format(classname=self.__class__.__name__,
|
||||
attrs=", ".join(l))
|
||||
|
||||
|
||||
def _sign(x):
|
||||
return int(copysign(1, x))
|
||||
|
||||
# vim:ts=4:sw=4:et
|
||||
@@ -1,17 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .tz import *
|
||||
from .tz import __doc__
|
||||
|
||||
#: Convenience constant providing a :class:`tzutc()` instance
|
||||
#:
|
||||
#: .. versionadded:: 2.7.0
|
||||
UTC = tzutc()
|
||||
|
||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
|
||||
"enfold", "datetime_ambiguous", "datetime_exists",
|
||||
"resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"]
|
||||
|
||||
|
||||
class DeprecatedTzFormatWarning(Warning):
|
||||
"""Warning raised when time zones are parsed from deprecated formats."""
|
||||
@@ -1,415 +0,0 @@
|
||||
from six import PY3
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from datetime import datetime, timedelta, tzinfo
|
||||
|
||||
|
||||
ZERO = timedelta(0)
|
||||
|
||||
__all__ = ['tzname_in_python2', 'enfold']
|
||||
|
||||
|
||||
def tzname_in_python2(namefunc):
|
||||
"""Change unicode output into bytestrings in Python 2
|
||||
|
||||
tzname() API changed in Python 3. It used to return bytes, but was changed
|
||||
to unicode strings
|
||||
"""
|
||||
def adjust_encoding(*args, **kwargs):
|
||||
name = namefunc(*args, **kwargs)
|
||||
if name is not None and not PY3:
|
||||
name = name.encode()
|
||||
|
||||
return name
|
||||
|
||||
return adjust_encoding
|
||||
|
||||
|
||||
# The following is adapted from Alexander Belopolsky's tz library
|
||||
# https://github.com/abalkin/tz
|
||||
if hasattr(datetime, 'fold'):
|
||||
# This is the pre-python 3.6 fold situation
|
||||
def enfold(dt, fold=1):
|
||||
"""
|
||||
Provides a unified interface for assigning the ``fold`` attribute to
|
||||
datetimes both before and after the implementation of PEP-495.
|
||||
|
||||
:param fold:
|
||||
The value for the ``fold`` attribute in the returned datetime. This
|
||||
should be either 0 or 1.
|
||||
|
||||
:return:
|
||||
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
|
||||
``fold`` for all versions of Python. In versions prior to
|
||||
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
|
||||
subclass of :py:class:`datetime.datetime` with the ``fold``
|
||||
attribute added, if ``fold`` is 1.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
return dt.replace(fold=fold)
|
||||
|
||||
else:
|
||||
class _DatetimeWithFold(datetime):
|
||||
"""
|
||||
This is a class designed to provide a PEP 495-compliant interface for
|
||||
Python versions before 3.6. It is used only for dates in a fold, so
|
||||
the ``fold`` attribute is fixed at ``1``.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def replace(self, *args, **kwargs):
|
||||
"""
|
||||
Return a datetime with the same attributes, except for those
|
||||
attributes given new values by whichever keyword arguments are
|
||||
specified. Note that tzinfo=None can be specified to create a naive
|
||||
datetime from an aware datetime with no conversion of date and time
|
||||
data.
|
||||
|
||||
This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
|
||||
return a ``datetime.datetime`` even if ``fold`` is unchanged.
|
||||
"""
|
||||
argnames = (
|
||||
'year', 'month', 'day', 'hour', 'minute', 'second',
|
||||
'microsecond', 'tzinfo'
|
||||
)
|
||||
|
||||
for arg, argname in zip(args, argnames):
|
||||
if argname in kwargs:
|
||||
raise TypeError('Duplicate argument: {}'.format(argname))
|
||||
|
||||
kwargs[argname] = arg
|
||||
|
||||
for argname in argnames:
|
||||
if argname not in kwargs:
|
||||
kwargs[argname] = getattr(self, argname)
|
||||
|
||||
dt_class = self.__class__ if kwargs.get('fold', 1) else datetime
|
||||
|
||||
return dt_class(**kwargs)
|
||||
|
||||
@property
|
||||
def fold(self):
|
||||
return 1
|
||||
|
||||
def enfold(dt, fold=1):
|
||||
"""
|
||||
Provides a unified interface for assigning the ``fold`` attribute to
|
||||
datetimes both before and after the implementation of PEP-495.
|
||||
|
||||
:param fold:
|
||||
The value for the ``fold`` attribute in the returned datetime. This
|
||||
should be either 0 or 1.
|
||||
|
||||
:return:
|
||||
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
|
||||
``fold`` for all versions of Python. In versions prior to
|
||||
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
|
||||
subclass of :py:class:`datetime.datetime` with the ``fold``
|
||||
attribute added, if ``fold`` is 1.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
if getattr(dt, 'fold', 0) == fold:
|
||||
return dt
|
||||
|
||||
args = dt.timetuple()[:6]
|
||||
args += (dt.microsecond, dt.tzinfo)
|
||||
|
||||
if fold:
|
||||
return _DatetimeWithFold(*args)
|
||||
else:
|
||||
return datetime(*args)
|
||||
|
||||
|
||||
def _validate_fromutc_inputs(f):
|
||||
"""
|
||||
The CPython version of ``fromutc`` checks that the input is a ``datetime``
|
||||
object and that ``self`` is attached as its ``tzinfo``.
|
||||
"""
|
||||
@wraps(f)
|
||||
def fromutc(self, dt):
|
||||
if not isinstance(dt, datetime):
|
||||
raise TypeError("fromutc() requires a datetime argument")
|
||||
if dt.tzinfo is not self:
|
||||
raise ValueError("dt.tzinfo is not self")
|
||||
|
||||
return f(self, dt)
|
||||
|
||||
return fromutc
|
||||
|
||||
|
||||
class _tzinfo(tzinfo):
|
||||
"""
|
||||
Base class for all ``dateutil`` ``tzinfo`` objects.
|
||||
"""
|
||||
|
||||
def is_ambiguous(self, dt):
|
||||
"""
|
||||
Whether or not the "wall time" of a given datetime is ambiguous in this
|
||||
zone.
|
||||
|
||||
:param dt:
|
||||
A :py:class:`datetime.datetime`, naive or time zone aware.
|
||||
|
||||
|
||||
:return:
|
||||
Returns ``True`` if ambiguous, ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
|
||||
dt = dt.replace(tzinfo=self)
|
||||
|
||||
wall_0 = enfold(dt, fold=0)
|
||||
wall_1 = enfold(dt, fold=1)
|
||||
|
||||
same_offset = wall_0.utcoffset() == wall_1.utcoffset()
|
||||
same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
|
||||
|
||||
return same_dt and not same_offset
|
||||
|
||||
def _fold_status(self, dt_utc, dt_wall):
|
||||
"""
|
||||
Determine the fold status of a "wall" datetime, given a representation
|
||||
of the same datetime as a (naive) UTC datetime. This is calculated based
|
||||
on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
|
||||
datetimes, and that this offset is the actual number of hours separating
|
||||
``dt_utc`` and ``dt_wall``.
|
||||
|
||||
:param dt_utc:
|
||||
Representation of the datetime as UTC
|
||||
|
||||
:param dt_wall:
|
||||
Representation of the datetime as "wall time". This parameter must
|
||||
either have a `fold` attribute or have a fold-naive
|
||||
:class:`datetime.tzinfo` attached, otherwise the calculation may
|
||||
fail.
|
||||
"""
|
||||
if self.is_ambiguous(dt_wall):
|
||||
delta_wall = dt_wall - dt_utc
|
||||
_fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
|
||||
else:
|
||||
_fold = 0
|
||||
|
||||
return _fold
|
||||
|
||||
def _fold(self, dt):
|
||||
return getattr(dt, 'fold', 0)
|
||||
|
||||
def _fromutc(self, dt):
|
||||
"""
|
||||
Given a timezone-aware datetime in a given timezone, calculates a
|
||||
timezone-aware datetime in a new timezone.
|
||||
|
||||
Since this is the one time that we *know* we have an unambiguous
|
||||
datetime object, we take this opportunity to determine whether the
|
||||
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
||||
occurence, chronologically, of the ambiguous datetime).
|
||||
|
||||
:param dt:
|
||||
A timezone-aware :class:`datetime.datetime` object.
|
||||
"""
|
||||
|
||||
# Re-implement the algorithm from Python's datetime.py
|
||||
dtoff = dt.utcoffset()
|
||||
if dtoff is None:
|
||||
raise ValueError("fromutc() requires a non-None utcoffset() "
|
||||
"result")
|
||||
|
||||
# The original datetime.py code assumes that `dst()` defaults to
|
||||
# zero during ambiguous times. PEP 495 inverts this presumption, so
|
||||
# for pre-PEP 495 versions of python, we need to tweak the algorithm.
|
||||
dtdst = dt.dst()
|
||||
if dtdst is None:
|
||||
raise ValueError("fromutc() requires a non-None dst() result")
|
||||
delta = dtoff - dtdst
|
||||
|
||||
dt += delta
|
||||
# Set fold=1 so we can default to being in the fold for
|
||||
# ambiguous dates.
|
||||
dtdst = enfold(dt, fold=1).dst()
|
||||
if dtdst is None:
|
||||
raise ValueError("fromutc(): dt.dst gave inconsistent "
|
||||
"results; cannot convert")
|
||||
return dt + dtdst
|
||||
|
||||
@_validate_fromutc_inputs
|
||||
def fromutc(self, dt):
|
||||
"""
|
||||
Given a timezone-aware datetime in a given timezone, calculates a
|
||||
timezone-aware datetime in a new timezone.
|
||||
|
||||
Since this is the one time that we *know* we have an unambiguous
|
||||
datetime object, we take this opportunity to determine whether the
|
||||
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
||||
occurance, chronologically, of the ambiguous datetime).
|
||||
|
||||
:param dt:
|
||||
A timezone-aware :class:`datetime.datetime` object.
|
||||
"""
|
||||
dt_wall = self._fromutc(dt)
|
||||
|
||||
# Calculate the fold status given the two datetimes.
|
||||
_fold = self._fold_status(dt, dt_wall)
|
||||
|
||||
# Set the default fold value for ambiguous dates
|
||||
return enfold(dt_wall, fold=_fold)
|
||||
|
||||
|
||||
class tzrangebase(_tzinfo):
|
||||
"""
|
||||
This is an abstract base class for time zones represented by an annual
|
||||
transition into and out of DST. Child classes should implement the following
|
||||
methods:
|
||||
|
||||
* ``__init__(self, *args, **kwargs)``
|
||||
* ``transitions(self, year)`` - this is expected to return a tuple of
|
||||
datetimes representing the DST on and off transitions in standard
|
||||
time.
|
||||
|
||||
A fully initialized ``tzrangebase`` subclass should also provide the
|
||||
following attributes:
|
||||
* ``hasdst``: Boolean whether or not the zone uses DST.
|
||||
* ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
|
||||
representing the respective UTC offsets.
|
||||
* ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
|
||||
abbreviations in DST and STD, respectively.
|
||||
* ``_hasdst``: Whether or not the zone has DST.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
def __init__(self):
|
||||
raise NotImplementedError('tzrangebase is an abstract base class')
|
||||
|
||||
def utcoffset(self, dt):
|
||||
isdst = self._isdst(dt)
|
||||
|
||||
if isdst is None:
|
||||
return None
|
||||
elif isdst:
|
||||
return self._dst_offset
|
||||
else:
|
||||
return self._std_offset
|
||||
|
||||
def dst(self, dt):
|
||||
isdst = self._isdst(dt)
|
||||
|
||||
if isdst is None:
|
||||
return None
|
||||
elif isdst:
|
||||
return self._dst_base_offset
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
@tzname_in_python2
|
||||
def tzname(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_abbr
|
||||
else:
|
||||
return self._std_abbr
|
||||
|
||||
def fromutc(self, dt):
|
||||
""" Given a datetime in UTC, return local time """
|
||||
if not isinstance(dt, datetime):
|
||||
raise TypeError("fromutc() requires a datetime argument")
|
||||
|
||||
if dt.tzinfo is not self:
|
||||
raise ValueError("dt.tzinfo is not self")
|
||||
|
||||
# Get transitions - if there are none, fixed offset
|
||||
transitions = self.transitions(dt.year)
|
||||
if transitions is None:
|
||||
return dt + self.utcoffset(dt)
|
||||
|
||||
# Get the transition times in UTC
|
||||
dston, dstoff = transitions
|
||||
|
||||
dston -= self._std_offset
|
||||
dstoff -= self._std_offset
|
||||
|
||||
utc_transitions = (dston, dstoff)
|
||||
dt_utc = dt.replace(tzinfo=None)
|
||||
|
||||
isdst = self._naive_isdst(dt_utc, utc_transitions)
|
||||
|
||||
if isdst:
|
||||
dt_wall = dt + self._dst_offset
|
||||
else:
|
||||
dt_wall = dt + self._std_offset
|
||||
|
||||
_fold = int(not isdst and self.is_ambiguous(dt_wall))
|
||||
|
||||
return enfold(dt_wall, fold=_fold)
|
||||
|
||||
def is_ambiguous(self, dt):
|
||||
"""
|
||||
Whether or not the "wall time" of a given datetime is ambiguous in this
|
||||
zone.
|
||||
|
||||
:param dt:
|
||||
A :py:class:`datetime.datetime`, naive or time zone aware.
|
||||
|
||||
|
||||
:return:
|
||||
Returns ``True`` if ambiguous, ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
if not self.hasdst:
|
||||
return False
|
||||
|
||||
start, end = self.transitions(dt.year)
|
||||
|
||||
dt = dt.replace(tzinfo=None)
|
||||
return (end <= dt < end + self._dst_base_offset)
|
||||
|
||||
def _isdst(self, dt):
|
||||
if not self.hasdst:
|
||||
return False
|
||||
elif dt is None:
|
||||
return None
|
||||
|
||||
transitions = self.transitions(dt.year)
|
||||
|
||||
if transitions is None:
|
||||
return False
|
||||
|
||||
dt = dt.replace(tzinfo=None)
|
||||
|
||||
isdst = self._naive_isdst(dt, transitions)
|
||||
|
||||
# Handle ambiguous dates
|
||||
if not isdst and self.is_ambiguous(dt):
|
||||
return not self._fold(dt)
|
||||
else:
|
||||
return isdst
|
||||
|
||||
def _naive_isdst(self, dt, transitions):
|
||||
dston, dstoff = transitions
|
||||
|
||||
dt = dt.replace(tzinfo=None)
|
||||
|
||||
if dston < dstoff:
|
||||
isdst = dston <= dt < dstoff
|
||||
else:
|
||||
isdst = not dstoff <= dt < dston
|
||||
|
||||
return isdst
|
||||
|
||||
@property
|
||||
def _dst_base_offset(self):
|
||||
return self._dst_offset - self._std_offset
|
||||
|
||||
__hash__ = None
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(...)" % self.__class__.__name__
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
@@ -1,49 +0,0 @@
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class _TzSingleton(type):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instance = None
|
||||
super(_TzSingleton, cls).__init__(*args, **kwargs)
|
||||
|
||||
def __call__(cls):
|
||||
if cls.__instance is None:
|
||||
cls.__instance = super(_TzSingleton, cls).__call__()
|
||||
return cls.__instance
|
||||
|
||||
class _TzFactory(type):
|
||||
def instance(cls, *args, **kwargs):
|
||||
"""Alternate constructor that returns a fresh instance"""
|
||||
return type.__call__(cls, *args, **kwargs)
|
||||
|
||||
|
||||
class _TzOffsetFactory(_TzFactory):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instances = {}
|
||||
|
||||
def __call__(cls, name, offset):
|
||||
if isinstance(offset, timedelta):
|
||||
key = (name, offset.total_seconds())
|
||||
else:
|
||||
key = (name, offset)
|
||||
|
||||
instance = cls.__instances.get(key, None)
|
||||
if instance is None:
|
||||
instance = cls.__instances.setdefault(key,
|
||||
cls.instance(name, offset))
|
||||
return instance
|
||||
|
||||
|
||||
class _TzStrFactory(_TzFactory):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instances = {}
|
||||
|
||||
def __call__(cls, s, posix_offset=False):
|
||||
key = (s, posix_offset)
|
||||
instance = cls.__instances.get(key, None)
|
||||
|
||||
if instance is None:
|
||||
instance = cls.__instances.setdefault(key,
|
||||
cls.instance(s, posix_offset))
|
||||
return instance
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
# This code was originally contributed by Jeffrey Harris.
|
||||
import datetime
|
||||
import struct
|
||||
|
||||
from six.moves import winreg
|
||||
from six import text_type
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
except ValueError:
|
||||
# ValueError is raised on non-Windows systems for some horrible reason.
|
||||
raise ImportError("Running tzwin on non-Windows system")
|
||||
|
||||
from ._common import tzrangebase
|
||||
|
||||
__all__ = ["tzwin", "tzwinlocal", "tzres"]
|
||||
|
||||
ONEWEEK = datetime.timedelta(7)
|
||||
|
||||
TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
|
||||
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
||||
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
||||
|
||||
|
||||
def _settzkeyname():
|
||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
try:
|
||||
winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
||||
TZKEYNAME = TZKEYNAMENT
|
||||
except WindowsError:
|
||||
TZKEYNAME = TZKEYNAME9X
|
||||
handle.Close()
|
||||
return TZKEYNAME
|
||||
|
||||
|
||||
TZKEYNAME = _settzkeyname()
|
||||
|
||||
|
||||
class tzres(object):
|
||||
"""
|
||||
Class for accessing `tzres.dll`, which contains timezone name related
|
||||
resources.
|
||||
|
||||
.. versionadded:: 2.5.0
|
||||
"""
|
||||
p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char
|
||||
|
||||
def __init__(self, tzres_loc='tzres.dll'):
|
||||
# Load the user32 DLL so we can load strings from tzres
|
||||
user32 = ctypes.WinDLL('user32')
|
||||
|
||||
# Specify the LoadStringW function
|
||||
user32.LoadStringW.argtypes = (wintypes.HINSTANCE,
|
||||
wintypes.UINT,
|
||||
wintypes.LPWSTR,
|
||||
ctypes.c_int)
|
||||
|
||||
self.LoadStringW = user32.LoadStringW
|
||||
self._tzres = ctypes.WinDLL(tzres_loc)
|
||||
self.tzres_loc = tzres_loc
|
||||
|
||||
def load_name(self, offset):
|
||||
"""
|
||||
Load a timezone name from a DLL offset (integer).
|
||||
|
||||
>>> from dateutil.tzwin import tzres
|
||||
>>> tzr = tzres()
|
||||
>>> print(tzr.load_name(112))
|
||||
'Eastern Standard Time'
|
||||
|
||||
:param offset:
|
||||
A positive integer value referring to a string from the tzres dll.
|
||||
|
||||
..note:
|
||||
Offsets found in the registry are generally of the form
|
||||
`@tzres.dll,-114`. The offset in this case if 114, not -114.
|
||||
|
||||
"""
|
||||
resource = self.p_wchar()
|
||||
lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR)
|
||||
nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0)
|
||||
return resource[:nchar]
|
||||
|
||||
def name_from_string(self, tzname_str):
|
||||
"""
|
||||
Parse strings as returned from the Windows registry into the time zone
|
||||
name as defined in the registry.
|
||||
|
||||
>>> from dateutil.tzwin import tzres
|
||||
>>> tzr = tzres()
|
||||
>>> print(tzr.name_from_string('@tzres.dll,-251'))
|
||||
'Dateline Daylight Time'
|
||||
>>> print(tzr.name_from_string('Eastern Standard Time'))
|
||||
'Eastern Standard Time'
|
||||
|
||||
:param tzname_str:
|
||||
A timezone name string as returned from a Windows registry key.
|
||||
|
||||
:return:
|
||||
Returns the localized timezone string from tzres.dll if the string
|
||||
is of the form `@tzres.dll,-offset`, else returns the input string.
|
||||
"""
|
||||
if not tzname_str.startswith('@'):
|
||||
return tzname_str
|
||||
|
||||
name_splt = tzname_str.split(',-')
|
||||
try:
|
||||
offset = int(name_splt[1])
|
||||
except:
|
||||
raise ValueError("Malformed timezone string.")
|
||||
|
||||
return self.load_name(offset)
|
||||
|
||||
|
||||
class tzwinbase(tzrangebase):
|
||||
"""tzinfo class based on win32's timezones available in the registry."""
|
||||
def __init__(self):
|
||||
raise NotImplementedError('tzwinbase is an abstract base class')
|
||||
|
||||
def __eq__(self, other):
|
||||
# Compare on all relevant dimensions, including name.
|
||||
if not isinstance(other, tzwinbase):
|
||||
return NotImplemented
|
||||
|
||||
return (self._std_offset == other._std_offset and
|
||||
self._dst_offset == other._dst_offset and
|
||||
self._stddayofweek == other._stddayofweek and
|
||||
self._dstdayofweek == other._dstdayofweek and
|
||||
self._stdweeknumber == other._stdweeknumber and
|
||||
self._dstweeknumber == other._dstweeknumber and
|
||||
self._stdhour == other._stdhour and
|
||||
self._dsthour == other._dsthour and
|
||||
self._stdminute == other._stdminute and
|
||||
self._dstminute == other._dstminute and
|
||||
self._std_abbr == other._std_abbr and
|
||||
self._dst_abbr == other._dst_abbr)
|
||||
|
||||
@staticmethod
|
||||
def list():
|
||||
"""Return a list of all time zones known to the system."""
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
with winreg.OpenKey(handle, TZKEYNAME) as tzkey:
|
||||
result = [winreg.EnumKey(tzkey, i)
|
||||
for i in range(winreg.QueryInfoKey(tzkey)[0])]
|
||||
return result
|
||||
|
||||
def display(self):
|
||||
return self._display
|
||||
|
||||
def transitions(self, year):
|
||||
"""
|
||||
For a given year, get the DST on and off transition times, expressed
|
||||
always on the standard time side. For zones with no transitions, this
|
||||
function returns ``None``.
|
||||
|
||||
:param year:
|
||||
The year whose transitions you would like to query.
|
||||
|
||||
:return:
|
||||
Returns a :class:`tuple` of :class:`datetime.datetime` objects,
|
||||
``(dston, dstoff)`` for zones with an annual DST transition, or
|
||||
``None`` for fixed offset zones.
|
||||
"""
|
||||
|
||||
if not self.hasdst:
|
||||
return None
|
||||
|
||||
dston = picknthweekday(year, self._dstmonth, self._dstdayofweek,
|
||||
self._dsthour, self._dstminute,
|
||||
self._dstweeknumber)
|
||||
|
||||
dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek,
|
||||
self._stdhour, self._stdminute,
|
||||
self._stdweeknumber)
|
||||
|
||||
# Ambiguous dates default to the STD side
|
||||
dstoff -= self._dst_base_offset
|
||||
|
||||
return dston, dstoff
|
||||
|
||||
def _get_hasdst(self):
|
||||
return self._dstmonth != 0
|
||||
|
||||
@property
|
||||
def _dst_base_offset(self):
|
||||
return self._dst_base_offset_
|
||||
|
||||
|
||||
class tzwin(tzwinbase):
|
||||
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name)
|
||||
with winreg.OpenKey(handle, tzkeyname) as tzkey:
|
||||
keydict = valuestodict(tzkey)
|
||||
|
||||
self._std_abbr = keydict["Std"]
|
||||
self._dst_abbr = keydict["Dlt"]
|
||||
|
||||
self._display = keydict["Display"]
|
||||
|
||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||
tup = struct.unpack("=3l16h", keydict["TZI"])
|
||||
stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
||||
dstoffset = stdoffset-tup[2] # + DaylightBias * -1
|
||||
self._std_offset = datetime.timedelta(minutes=stdoffset)
|
||||
self._dst_offset = datetime.timedelta(minutes=dstoffset)
|
||||
|
||||
# for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
|
||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
|
||||
(self._stdmonth,
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stdhour,
|
||||
self._stdminute) = tup[4:9]
|
||||
|
||||
(self._dstmonth,
|
||||
self._dstdayofweek, # Sunday = 0
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dsthour,
|
||||
self._dstminute) = tup[12:17]
|
||||
|
||||
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
||||
self.hasdst = self._get_hasdst()
|
||||
|
||||
def __repr__(self):
|
||||
return "tzwin(%s)" % repr(self._name)
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, (self._name,))
|
||||
|
||||
|
||||
class tzwinlocal(tzwinbase):
|
||||
def __init__(self):
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
||||
keydict = valuestodict(tzlocalkey)
|
||||
|
||||
self._std_abbr = keydict["StandardName"]
|
||||
self._dst_abbr = keydict["DaylightName"]
|
||||
|
||||
try:
|
||||
tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME,
|
||||
sn=self._std_abbr)
|
||||
with winreg.OpenKey(handle, tzkeyname) as tzkey:
|
||||
_keydict = valuestodict(tzkey)
|
||||
self._display = _keydict["Display"]
|
||||
except OSError:
|
||||
self._display = None
|
||||
|
||||
stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
||||
dstoffset = stdoffset-keydict["DaylightBias"]
|
||||
|
||||
self._std_offset = datetime.timedelta(minutes=stdoffset)
|
||||
self._dst_offset = datetime.timedelta(minutes=dstoffset)
|
||||
|
||||
# For reasons unclear, in this particular key, the day of week has been
|
||||
# moved to the END of the SYSTEMTIME structure.
|
||||
tup = struct.unpack("=8h", keydict["StandardStart"])
|
||||
|
||||
(self._stdmonth,
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stdhour,
|
||||
self._stdminute) = tup[1:5]
|
||||
|
||||
self._stddayofweek = tup[7]
|
||||
|
||||
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
||||
|
||||
(self._dstmonth,
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dsthour,
|
||||
self._dstminute) = tup[1:5]
|
||||
|
||||
self._dstdayofweek = tup[7]
|
||||
|
||||
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
||||
self.hasdst = self._get_hasdst()
|
||||
|
||||
def __repr__(self):
|
||||
return "tzwinlocal()"
|
||||
|
||||
def __str__(self):
|
||||
# str will return the standard name, not the daylight name.
|
||||
return "tzwinlocal(%s)" % repr(self._std_abbr)
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, ())
|
||||
|
||||
|
||||
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
||||
""" dayofweek == 0 means Sunday, whichweek 5 means last instance """
|
||||
first = datetime.datetime(year, month, 1, hour, minute)
|
||||
|
||||
# This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6),
|
||||
# Because 7 % 7 = 0
|
||||
weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1)
|
||||
wd = weekdayone + ((whichweek - 1) * ONEWEEK)
|
||||
if (wd.month != month):
|
||||
wd -= ONEWEEK
|
||||
|
||||
return wd
|
||||
|
||||
|
||||
def valuestodict(key):
|
||||
"""Convert a registry key's values to a dictionary."""
|
||||
dout = {}
|
||||
size = winreg.QueryInfoKey(key)[1]
|
||||
tz_res = None
|
||||
|
||||
for i in range(size):
|
||||
key_name, value, dtype = winreg.EnumValue(key, i)
|
||||
if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN:
|
||||
# If it's a DWORD (32-bit integer), it's stored as unsigned - convert
|
||||
# that to a proper signed integer
|
||||
if value & (1 << 31):
|
||||
value = value - (1 << 32)
|
||||
elif dtype == winreg.REG_SZ:
|
||||
# If it's a reference to the tzres DLL, load the actual string
|
||||
if value.startswith('@tzres'):
|
||||
tz_res = tz_res or tzres()
|
||||
value = tz_res.name_from_string(value)
|
||||
|
||||
value = value.rstrip('\x00') # Remove trailing nulls
|
||||
|
||||
dout[key_name] = value
|
||||
|
||||
return dout
|
||||
@@ -1,2 +0,0 @@
|
||||
# tzwin has moved to dateutil.tz.win
|
||||
from .tz.win import *
|
||||
@@ -1,71 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module offers general convenience and utility functions for dealing with
|
||||
datetimes.
|
||||
|
||||
.. versionadded:: 2.7.0
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime, time
|
||||
|
||||
|
||||
def today(tzinfo=None):
|
||||
"""
|
||||
Returns a :py:class:`datetime` representing the current day at midnight
|
||||
|
||||
:param tzinfo:
|
||||
The time zone to attach (also used to determine the current day).
|
||||
|
||||
:return:
|
||||
A :py:class:`datetime.datetime` object representing the current day
|
||||
at midnight.
|
||||
"""
|
||||
|
||||
dt = datetime.now(tzinfo)
|
||||
return datetime.combine(dt.date(), time(0, tzinfo=tzinfo))
|
||||
|
||||
|
||||
def default_tzinfo(dt, tzinfo):
|
||||
"""
|
||||
Sets the the ``tzinfo`` parameter on naive datetimes only
|
||||
|
||||
This is useful for example when you are provided a datetime that may have
|
||||
either an implicit or explicit time zone, such as when parsing a time zone
|
||||
string.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> from dateutil.tz import tzoffset
|
||||
>>> from dateutil.parser import parse
|
||||
>>> from dateutil.utils import default_tzinfo
|
||||
>>> dflt_tz = tzoffset("EST", -18000)
|
||||
>>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz))
|
||||
2014-01-01 12:30:00+00:00
|
||||
>>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz))
|
||||
2014-01-01 12:30:00-05:00
|
||||
|
||||
:param dt:
|
||||
The datetime on which to replace the time zone
|
||||
|
||||
:param tzinfo:
|
||||
The :py:class:`datetime.tzinfo` subclass instance to assign to
|
||||
``dt`` if (and only if) it is naive.
|
||||
|
||||
:return:
|
||||
Returns an aware :py:class:`datetime.datetime`.
|
||||
"""
|
||||
if dt.tzinfo is not None:
|
||||
return dt
|
||||
else:
|
||||
return dt.replace(tzinfo=tzinfo)
|
||||
|
||||
|
||||
def within_delta(dt1, dt2, delta):
|
||||
"""
|
||||
Useful for comparing two datetimes that may a negilible difference
|
||||
to be considered equal.
|
||||
"""
|
||||
delta = abs(delta)
|
||||
difference = dt1 - dt2
|
||||
return -delta <= difference <= delta
|
||||
@@ -1,167 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import warnings
|
||||
import json
|
||||
|
||||
from tarfile import TarFile
|
||||
from pkgutil import get_data
|
||||
from io import BytesIO
|
||||
|
||||
from dateutil.tz import tzfile as _tzfile
|
||||
|
||||
__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"]
|
||||
|
||||
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
|
||||
METADATA_FN = 'METADATA'
|
||||
|
||||
|
||||
class tzfile(_tzfile):
|
||||
def __reduce__(self):
|
||||
return (gettz, (self._filename,))
|
||||
|
||||
|
||||
def getzoneinfofile_stream():
|
||||
try:
|
||||
return BytesIO(get_data(__name__, ZONEFILENAME))
|
||||
except IOError as e: # TODO switch to FileNotFoundError?
|
||||
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
|
||||
return None
|
||||
|
||||
|
||||
class ZoneInfoFile(object):
|
||||
def __init__(self, zonefile_stream=None):
|
||||
if zonefile_stream is not None:
|
||||
with TarFile.open(fileobj=zonefile_stream) as tf:
|
||||
self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
|
||||
for zf in tf.getmembers()
|
||||
if zf.isfile() and zf.name != METADATA_FN}
|
||||
# deal with links: They'll point to their parent object. Less
|
||||
# waste of memory
|
||||
links = {zl.name: self.zones[zl.linkname]
|
||||
for zl in tf.getmembers() if
|
||||
zl.islnk() or zl.issym()}
|
||||
self.zones.update(links)
|
||||
try:
|
||||
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
|
||||
metadata_str = metadata_json.read().decode('UTF-8')
|
||||
self.metadata = json.loads(metadata_str)
|
||||
except KeyError:
|
||||
# no metadata in tar file
|
||||
self.metadata = None
|
||||
else:
|
||||
self.zones = {}
|
||||
self.metadata = None
|
||||
|
||||
def get(self, name, default=None):
|
||||
"""
|
||||
Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
|
||||
for retrieving zones from the zone dictionary.
|
||||
|
||||
:param name:
|
||||
The name of the zone to retrieve. (Generally IANA zone names)
|
||||
|
||||
:param default:
|
||||
The value to return in the event of a missing key.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
|
||||
"""
|
||||
return self.zones.get(name, default)
|
||||
|
||||
|
||||
# The current API has gettz as a module function, although in fact it taps into
|
||||
# a stateful class. So as a workaround for now, without changing the API, we
|
||||
# will create a new "global" class instance the first time a user requests a
|
||||
# timezone. Ugly, but adheres to the api.
|
||||
#
|
||||
# TODO: Remove after deprecation period.
|
||||
_CLASS_ZONE_INSTANCE = []
|
||||
|
||||
|
||||
def get_zonefile_instance(new_instance=False):
|
||||
"""
|
||||
This is a convenience function which provides a :class:`ZoneInfoFile`
|
||||
instance using the data provided by the ``dateutil`` package. By default, it
|
||||
caches a single instance of the ZoneInfoFile object and returns that.
|
||||
|
||||
:param new_instance:
|
||||
If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
|
||||
used as the cached instance for the next call. Otherwise, new instances
|
||||
are created only as necessary.
|
||||
|
||||
:return:
|
||||
Returns a :class:`ZoneInfoFile` object.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
if new_instance:
|
||||
zif = None
|
||||
else:
|
||||
zif = getattr(get_zonefile_instance, '_cached_instance', None)
|
||||
|
||||
if zif is None:
|
||||
zif = ZoneInfoFile(getzoneinfofile_stream())
|
||||
|
||||
get_zonefile_instance._cached_instance = zif
|
||||
|
||||
return zif
|
||||
|
||||
|
||||
def gettz(name):
|
||||
"""
|
||||
This retrieves a time zone from the local zoneinfo tarball that is packaged
|
||||
with dateutil.
|
||||
|
||||
:param name:
|
||||
An IANA-style time zone name, as found in the zoneinfo file.
|
||||
|
||||
:return:
|
||||
Returns a :class:`dateutil.tz.tzfile` time zone object.
|
||||
|
||||
.. warning::
|
||||
It is generally inadvisable to use this function, and it is only
|
||||
provided for API compatibility with earlier versions. This is *not*
|
||||
equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
|
||||
time zone based on the inputs, favoring system zoneinfo. This is ONLY
|
||||
for accessing the dateutil-specific zoneinfo (which may be out of
|
||||
date compared to the system zoneinfo).
|
||||
|
||||
.. deprecated:: 2.6
|
||||
If you need to use a specific zoneinfofile over the system zoneinfo,
|
||||
instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
|
||||
:func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.
|
||||
|
||||
Use :func:`get_zonefile_instance` to retrieve an instance of the
|
||||
dateutil-provided zoneinfo.
|
||||
"""
|
||||
warnings.warn("zoneinfo.gettz() will be removed in future versions, "
|
||||
"to use the dateutil-provided zoneinfo files, instantiate a "
|
||||
"ZoneInfoFile object and use ZoneInfoFile.zones.get() "
|
||||
"instead. See the documentation for details.",
|
||||
DeprecationWarning)
|
||||
|
||||
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
||||
|
||||
|
||||
def gettz_db_metadata():
|
||||
""" Get the zonefile metadata
|
||||
|
||||
See `zonefile_metadata`_
|
||||
|
||||
:returns:
|
||||
A dictionary with the database metadata
|
||||
|
||||
.. deprecated:: 2.6
|
||||
See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
|
||||
query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
|
||||
"""
|
||||
warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
|
||||
"versions, to use the dateutil-provided zoneinfo files, "
|
||||
"ZoneInfoFile object and query the 'metadata' attribute "
|
||||
"instead. See the documentation for details.",
|
||||
DeprecationWarning)
|
||||
|
||||
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||
return _CLASS_ZONE_INSTANCE[0].metadata
|
||||
@@ -1,53 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
import json
|
||||
from subprocess import check_call
|
||||
from tarfile import TarFile
|
||||
|
||||
from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME
|
||||
|
||||
|
||||
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
||||
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
|
||||
|
||||
filename is the timezone tarball from ``ftp.iana.org/tz``.
|
||||
|
||||
"""
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||
moduledir = os.path.dirname(__file__)
|
||||
try:
|
||||
with TarFile.open(filename) as tf:
|
||||
for name in zonegroups:
|
||||
tf.extract(name, tmpdir)
|
||||
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
|
||||
try:
|
||||
check_call(["zic", "-d", zonedir] + filepaths)
|
||||
except OSError as e:
|
||||
_print_on_nosuchfile(e)
|
||||
raise
|
||||
# write metadata file
|
||||
with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
|
||||
json.dump(metadata, f, indent=4, sort_keys=True)
|
||||
target = os.path.join(moduledir, ZONEFILENAME)
|
||||
with TarFile.open(target, "w:%s" % format) as tf:
|
||||
for entry in os.listdir(zonedir):
|
||||
entrypath = os.path.join(zonedir, entry)
|
||||
tf.add(entrypath, entry)
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
def _print_on_nosuchfile(e):
|
||||
"""Print helpful troubleshooting message
|
||||
|
||||
e is an exception raised by subprocess.check_call()
|
||||
|
||||
"""
|
||||
if e.errno == 2:
|
||||
logging.error(
|
||||
"Could not find zic. Perhaps you need to install "
|
||||
"libc-bin or some other package that provides it, "
|
||||
"or it's not in your PATH?")
|
||||
@@ -1,5 +0,0 @@
|
||||
"""Run the EasyInstall command"""
|
||||
|
||||
if __name__ == '__main__':
|
||||
from setuptools.command.easy_install import main
|
||||
main()
|
||||
@@ -1,16 +0,0 @@
|
||||
Welcome to Kiwi
|
||||
===============
|
||||
|
||||
.. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=master
|
||||
:target: https://travis-ci.org/nucleic/kiwi
|
||||
|
||||
Kiwi is an efficient C++ implementation of the Cassowary constraint solving
|
||||
algorithm. Kiwi is an implementation of the algorithm based on the seminal
|
||||
Cassowary paper. It is *not* a refactoring of the original C++ solver. Kiwi
|
||||
has been designed from the ground up to be lightweight and fast. Kiwi ranges
|
||||
from 10x to 500x faster than the original Cassowary solver with typical use
|
||||
cases gaining a 40x improvement. Memory savings are consistently > 5x.
|
||||
|
||||
In addition to the C++ solver, Kiwi ships with hand-rolled Python bindings.
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,28 +0,0 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: kiwisolver
|
||||
Version: 1.0.1
|
||||
Summary: A fast implementation of the Cassowary constraint solver
|
||||
Home-page: https://github.com/nucleic/kiwi
|
||||
Author: The Nucleic Development Team
|
||||
Author-email: sccolbert@gmail.com
|
||||
License: UNKNOWN
|
||||
Description-Content-Type: UNKNOWN
|
||||
Platform: UNKNOWN
|
||||
Requires-Dist: setuptools
|
||||
|
||||
Welcome to Kiwi
|
||||
===============
|
||||
|
||||
.. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=master
|
||||
:target: https://travis-ci.org/nucleic/kiwi
|
||||
|
||||
Kiwi is an efficient C++ implementation of the Cassowary constraint solving
|
||||
algorithm. Kiwi is an implementation of the algorithm based on the seminal
|
||||
Cassowary paper. It is *not* a refactoring of the original C++ solver. Kiwi
|
||||
has been designed from the ground up to be lightweight and fast. Kiwi ranges
|
||||
from 10x to 500x faster than the original Cassowary solver with typical use
|
||||
cases gaining a 40x improvement. Memory savings are consistently > 5x.
|
||||
|
||||
In addition to the C++ solver, Kiwi ships with hand-rolled Python bindings.
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
kiwisolver-1.0.1.dist-info/DESCRIPTION.rst,sha256=Qy5sjKaN4toH_Q7EUHWgwRVmpxBgeGUwEcyB75zRwSI,676
|
||||
kiwisolver-1.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
kiwisolver-1.0.1.dist-info/METADATA,sha256=bFtjRI423l1EopYow7-FVdw80WP942cTW4I37mnnunU,1006
|
||||
kiwisolver-1.0.1.dist-info/RECORD,,
|
||||
kiwisolver-1.0.1.dist-info/WHEEL,sha256=xLbWRW0PO79-mIB5Y7BBxUY9L97LDdQCs-obTuQSLEg,109
|
||||
kiwisolver-1.0.1.dist-info/metadata.json,sha256=H-tQ6em1_t3jGeivswhlBiHFJ7CBAI6G_RZAsT-QHCs,535
|
||||
kiwisolver-1.0.1.dist-info/top_level.txt,sha256=xqwWj7oSHlpIjcw2QMJb8puTFPdjDBO78AZp9gjTh9c,11
|
||||
kiwisolver.cpython-36m-x86_64-linux-gnu.so,sha256=4GkBV_S_pJ93VLqTL5uPrsTtcIaTx6RIvdblto_AmVg,3860760
|
||||
@@ -1,5 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.30.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp36-cp36m-manylinux1_x86_64
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "sccolbert@gmail.com", "name": "The Nucleic Development Team", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/nucleic/kiwi"}}}, "extras": [], "generator": "bdist_wheel (0.30.0)", "metadata_version": "2.0", "name": "kiwisolver", "run_requires": [{"requires": ["setuptools"]}], "summary": "A fast implementation of the Cassowary constraint solver", "version": "1.0.1"}
|
||||
@@ -1 +0,0 @@
|
||||
kiwisolver
|
||||
@@ -1,2 +0,0 @@
|
||||
import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('mpl_toolkits',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('mpl_toolkits', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('mpl_toolkits', [os.path.dirname(p)])));m = m or sys.modules.setdefault('mpl_toolkits', types.ModuleType('mpl_toolkits'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
|
||||
import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('mpl_toolkits',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('mpl_toolkits', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('mpl_toolkits', [os.path.dirname(p)])));m = m or sys.modules.setdefault('mpl_toolkits', types.ModuleType('mpl_toolkits'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
|
||||
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,33 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: matplotlib
|
||||
Version: 3.0.2
|
||||
Summary: Python plotting package
|
||||
Home-page: http://matplotlib.org
|
||||
Author: John D. Hunter, Michael Droettboom
|
||||
Author-email: matplotlib-users@python.org
|
||||
License: BSD
|
||||
Download-URL: http://matplotlib.org/users/installing.html
|
||||
Platform: any
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Science/Research
|
||||
Classifier: License :: OSI Approved :: Python Software Foundation License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Topic :: Scientific/Engineering :: Visualization
|
||||
Requires-Python: >=3.5
|
||||
Requires-Dist: numpy (>=1.10.0)
|
||||
Requires-Dist: cycler (>=0.10)
|
||||
Requires-Dist: kiwisolver (>=1.0.1)
|
||||
Requires-Dist: pyparsing (!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1)
|
||||
Requires-Dist: python-dateutil (>=2.1)
|
||||
|
||||
|
||||
Matplotlib strives to produce publication quality 2D graphics
|
||||
for interactive graphing, scientific publishing, user interface
|
||||
development and web application servers targeting multiple user
|
||||
interfaces and hardcopy output formats.
|
||||
|
||||
|
||||
@@ -1,919 +0,0 @@
|
||||
__pycache__/pylab.cpython-36.pyc,,
|
||||
matplotlib-3.0.2-py3.6-nspkg.pth,sha256=HBCg6BgtP04BPqHtaNyC13Cbp9tWa8jd68ltGEV1XF8,1138
|
||||
matplotlib-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
matplotlib-3.0.2.dist-info/METADATA,sha256=fs4NGrFCD56DFwQffsSExcU818TFY8T9OTinV1dNP5w,1226
|
||||
matplotlib-3.0.2.dist-info/RECORD,,
|
||||
matplotlib-3.0.2.dist-info/WHEEL,sha256=d2ILPScH-y2UwGxsW1PeA2TT-KW0Git4AJ6LeOK8sQo,109
|
||||
matplotlib-3.0.2.dist-info/namespace_packages.txt,sha256=LQMWCv385LtvVcrCFmS8Kk1axcWAaUG9ycvuMhV6yoA,26
|
||||
matplotlib-3.0.2.dist-info/top_level.txt,sha256=9tEw2ni8DdgX8CceoYHqSH1s50vrJ9SDfgtLIG8e3Y4,30
|
||||
matplotlib/.libs/libpng16-cfdb1654.so.16.21.0,sha256=Fo8LBDWTuCclLkpSng_KP5pI7wcQtuXA9opT1FFkXl0,275648
|
||||
matplotlib/.libs/libz-a147dcb0.so.1.2.3,sha256=1IGoOjRpujOMRn7cZ29ERtAxBt6SxTUlRLBkSqa_lsk,87848
|
||||
matplotlib/__init__.py,sha256=pWjrFCjP1acCOKaKs7AMHt1xVBiNQ_EXQtq6DUJJQUU,64067
|
||||
matplotlib/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/_animation_data.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/_cm.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/_cm_listed.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/_color_data.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/_constrained_layout.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/_layoutbox.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/_mathtext_data.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/_pylab_helpers.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/_version.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/afm.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/animation.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/artist.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/axis.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/backend_bases.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/backend_managers.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/backend_tools.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/bezier.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/blocking_input.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/category.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/cm.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/collections.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/colorbar.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/colors.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/container.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/contour.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/dates.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/docstring.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/dviread.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/figure.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/font_manager.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/fontconfig_pattern.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/gridspec.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/hatch.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/image.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/legend.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/legend_handler.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/lines.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/markers.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/mathtext.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/mlab.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/offsetbox.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/patches.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/path.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/patheffects.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/pylab.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/pyplot.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/quiver.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/rcsetup.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/sankey.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/scale.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/spines.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/stackplot.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/streamplot.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/table.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/texmanager.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/text.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/textpath.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/ticker.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/tight_bbox.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/tight_layout.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/transforms.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/type1font.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/units.cpython-36.pyc,,
|
||||
matplotlib/__pycache__/widgets.cpython-36.pyc,,
|
||||
matplotlib/_animation_data.py,sha256=CCdf8YwNX_08FS3YFPYzr2wi3id_WXIKLHrqM50HM8g,6157
|
||||
matplotlib/_cm.py,sha256=C3xi_H7o8WF6qSv3Jl0DA1T2vbT-4wIDLFYfuJe5Zpg,66609
|
||||
matplotlib/_cm_listed.py,sha256=EpTjQ6pZ9E_UeY4kDa6fU9za_VHBvhuOmCG6fwNRvlk,98362
|
||||
matplotlib/_color_data.py,sha256=9hUzbyqLpEe-2LjEeAN3ja2ANuryNvtTseU8vuJXfsI,34776
|
||||
matplotlib/_constrained_layout.py,sha256=1ygKBGfY0ttpPFhEWucL0wVnwYP-U_ZX8OxHlCUXXjQ,29304
|
||||
matplotlib/_contour.cpython-36m-x86_64-linux-gnu.so,sha256=Gd6D_VYbA-imr5k186kroDG91DrOUYiltJS3QuARruQ,95144
|
||||
matplotlib/_image.cpython-36m-x86_64-linux-gnu.so,sha256=akH-urj3-vOY9iYC-N9lORYlpSVVMPUKMBnCAVPrqc0,242496
|
||||
matplotlib/_layoutbox.py,sha256=yXrJA2hBYKdzPNzbrsLl0Lv8zAPtnPulQ9-BbfhcVWM,24354
|
||||
matplotlib/_mathtext_data.py,sha256=CmKFRW6mXCJqgZSQaiNOSG_VUn9WiSx5Hrg-4qKIn14,89371
|
||||
matplotlib/_path.cpython-36m-x86_64-linux-gnu.so,sha256=jEhQzvD031IYQRSbo4Fxobi5KpZF-NV_7iuDDRaVBDU,190216
|
||||
matplotlib/_png.cpython-36m-x86_64-linux-gnu.so,sha256=DlT39C99TemBT-i-7MuJuwJRg6eVwRhMMR4AxInU2QI,48144
|
||||
matplotlib/_pylab_helpers.py,sha256=lC5IY7NOGCPVD5brJj-PdtJkzA53Fs4hrn9exdzWDFg,3528
|
||||
matplotlib/_qhull.cpython-36m-x86_64-linux-gnu.so,sha256=dpM763p27zLpJIW8u045wQW71Oz3JwP9BHCHW6M-pHY,382640
|
||||
matplotlib/_tri.cpython-36m-x86_64-linux-gnu.so,sha256=7InLl8WYUN4CyAtbrAWCXzycxTUs7_xNZx4QZKcifZg,128616
|
||||
matplotlib/_version.py,sha256=bHCJPWh4TT1eI2_VgNQWyLUAJMklrvz_FkivkMGVXmk,471
|
||||
matplotlib/afm.py,sha256=Cfj2v5Rgsr5GaDzuKySLd-aqsBdJBLX2T1gt7TQ175o,16934
|
||||
matplotlib/animation.py,sha256=WyFTZBvyIN2tmvnT9LzlGZDOvqWgPn0SHOfzl0e1LlY,67680
|
||||
matplotlib/artist.py,sha256=VbAnAHa4CPr39s_99S0yAD9-tBUMPxc-BLvhWeykSLk,48885
|
||||
matplotlib/axes/__init__.py,sha256=npQuBvs_xEBEGUP2-BBZzCrelsAQYgB1U96kSZTSWIs,46
|
||||
matplotlib/axes/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/axes/__pycache__/_axes.cpython-36.pyc,,
|
||||
matplotlib/axes/__pycache__/_base.cpython-36.pyc,,
|
||||
matplotlib/axes/__pycache__/_subplots.cpython-36.pyc,,
|
||||
matplotlib/axes/_axes.py,sha256=o_hBMnccGhT_BedP7n12Kk6Y-qhEmL7S3guI5EF7r_M,306901
|
||||
matplotlib/axes/_base.py,sha256=-gd9r0IAp785gRHpHpaN1IibgC5J069NNPKH2d_UbHQ,156195
|
||||
matplotlib/axes/_subplots.py,sha256=pPG7GjDBFGDnjVXdojw3_ROHUO5kjZM2s7FsUwIH7sE,9479
|
||||
matplotlib/axis.py,sha256=s2rcQiyNOCskQ3e4J9YeG-dI0GMbH8v3SWbrC7ymtPo,90811
|
||||
matplotlib/backend_bases.py,sha256=7EaSuF58KKjm5sRYnmsUECkSJc0BcbsYmGd3gMpcpJM,111487
|
||||
matplotlib/backend_managers.py,sha256=R3ZGS7NiyfAYutPX9KRqRXiZur8VVFb2_yQPNKtQ2Os,12983
|
||||
matplotlib/backend_tools.py,sha256=nCruryzfTxOT0v8wyQPzPvCuNdOx5eWXTiiVniGoQmg,35810
|
||||
matplotlib/backends/__init__.py,sha256=Y2fIYrWuxw3DsTGBgaYTkoExgiv5V-fpFMoL6Xluv1U,3593
|
||||
matplotlib/backends/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/_backend_tk.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/_gtk3_compat.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_agg.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_cairo.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_gtk3.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_gtk3agg.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_gtk3cairo.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_macosx.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_mixed.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_nbagg.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_pdf.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_pgf.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_ps.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_qt4.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_qt4agg.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_qt4cairo.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_qt5.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_qt5agg.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_qt5cairo.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_svg.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_template.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_tkagg.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_tkcairo.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_webagg.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_webagg_core.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_wx.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_wxagg.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/backend_wxcairo.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/qt_compat.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/tkagg.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/windowing.cpython-36.pyc,,
|
||||
matplotlib/backends/__pycache__/wx_compat.cpython-36.pyc,,
|
||||
matplotlib/backends/_backend_agg.cpython-36m-x86_64-linux-gnu.so,sha256=7egU7A_XSGu5zAhXxcs7ubTrUwDx9wCah09ET3u9BTg,358384
|
||||
matplotlib/backends/_backend_tk.py,sha256=oG6iWxbo96fmE24Q1-2p62MX2sSXwp-bDsCiteqw7bw,37878
|
||||
matplotlib/backends/_gtk3_compat.py,sha256=_mQjgaLq7PAdWCmkYkN3ZEc7SFms4T47Y6X1bmc3UlI,1458
|
||||
matplotlib/backends/_tkagg.cpython-36m-x86_64-linux-gnu.so,sha256=ghIFRXt4_3htN74rENcBbpBc_Ly2pRg94F6h_xxE7vI,29080
|
||||
matplotlib/backends/backend_agg.py,sha256=oWgXuSgkOr_w_Ix3xB07QwaW34CIrDeFr3Ar38KWLnI,21136
|
||||
matplotlib/backends/backend_cairo.py,sha256=F_Er53Cssgiaz22qKUExxLvgBssxmsl0vRTenkckDJA,23049
|
||||
matplotlib/backends/backend_gtk3.py,sha256=7yldMkTpLnLqd9GLi905Ba2Se0gBe2bNNjrojNYDKMM,34061
|
||||
matplotlib/backends/backend_gtk3agg.py,sha256=Sdv_sVpJab05fFqZEz4a8hv2jB6194l0S7I9kioDCBc,2863
|
||||
matplotlib/backends/backend_gtk3cairo.py,sha256=qvEc-eOfNhYhvG_4COnmCt3e_L-dInGMMnWJY3EG1s8,1516
|
||||
matplotlib/backends/backend_macosx.py,sha256=cQLOUeHfteu3UzK5VhdBwNRohFidcNGsJ9NNCpcx52Y,6534
|
||||
matplotlib/backends/backend_mixed.py,sha256=w0npmW09OKYhaHlpaKXY9vBhKNpUrL_gplxhjlm_re0,5738
|
||||
matplotlib/backends/backend_nbagg.py,sha256=981WrP82fEZJIdND77C2JW4DpS3k82J6PyXthy18-jM,8707
|
||||
matplotlib/backends/backend_pdf.py,sha256=L6GwqJKiPaFlqPRuhFbJQ9xGhZf8DGkL-FQ5W_nSamw,96965
|
||||
matplotlib/backends/backend_pgf.py,sha256=8a9VSoU32ZatMBik36IjmrM56GkU0QFc3w1o3z3y89w,43189
|
||||
matplotlib/backends/backend_ps.py,sha256=M47hHy6DgqRev1emHxv-K8diIoP5aYsNKW1sMex2h2A,61117
|
||||
matplotlib/backends/backend_qt4.py,sha256=x9KHRXMxJfmlJ-6lMdmKrw9cGe5RlZAAGjk1Ga1xX2c,410
|
||||
matplotlib/backends/backend_qt4agg.py,sha256=xTZMUL161hqcsOKSeU4sEs9kYloQrC7_OJjfyuYUAp0,245
|
||||
matplotlib/backends/backend_qt4cairo.py,sha256=B-LD_AECxVVsZA6Zb-oxoN39iM-GUNl81LR7nVaJ8q4,159
|
||||
matplotlib/backends/backend_qt5.py,sha256=HTxAS2-cOPD_pS40BP4foUl-UfQb3XHmQrNJsuWE8Vs,41475
|
||||
matplotlib/backends/backend_qt5agg.py,sha256=plgjHJqJ0IawKuEH8Re_mTOLLrH90mRcMs65nbdTLvE,3237
|
||||
matplotlib/backends/backend_qt5cairo.py,sha256=wTVbxWW9f5REOesXDIzI2eSmE5HS-nwWNcg2m-YvyLI,1977
|
||||
matplotlib/backends/backend_svg.py,sha256=I_-tR0sEpRFt7jlM8S-dJX8vgjwMifEJTa5N3maJWC0,45518
|
||||
matplotlib/backends/backend_template.py,sha256=drJi5J7r4ZMpmwvYiVp3uVfWDJ0VtZSEd2A4OD93fUU,9149
|
||||
matplotlib/backends/backend_tkagg.py,sha256=WMslLWYmtxlmAaBH4tx4HjmRDWMKiSV91KHF9yeMRng,676
|
||||
matplotlib/backends/backend_tkcairo.py,sha256=dVCh7ZD_2OR0DBQ0N3icD8cDV1SeEzCsRja446wWhPw,1069
|
||||
matplotlib/backends/backend_webagg.py,sha256=kqDsqw1a0mZA3-ouBGegg8BlWys5iAA0-fAtxTvUZ5o,11124
|
||||
matplotlib/backends/backend_webagg_core.py,sha256=c8FqOMph-FdYJ-kNauNG23enXWwZwbmQ3QzjhYud4IM,17554
|
||||
matplotlib/backends/backend_wx.py,sha256=gQTIV29utVqq_W6iqRE5gqGwhDZ3sPg2s0fxXDkmcCY,74559
|
||||
matplotlib/backends/backend_wxagg.py,sha256=TGdhDULgRzqhLNOzq7QAt0oe_AqHoT-IyZOO8RlE4Bw,4015
|
||||
matplotlib/backends/backend_wxcairo.py,sha256=VC5TyJaX8TPLSgHv5ckAreoGrY_KiNRMQjVInMLlcFk,1843
|
||||
matplotlib/backends/qt_compat.py,sha256=gdDU16Oe1HfwJJSGmvLvEJmrKDX6T8Tt1WiSUITR3qU,6658
|
||||
matplotlib/backends/qt_editor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
matplotlib/backends/qt_editor/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/backends/qt_editor/__pycache__/figureoptions.cpython-36.pyc,,
|
||||
matplotlib/backends/qt_editor/__pycache__/formlayout.cpython-36.pyc,,
|
||||
matplotlib/backends/qt_editor/__pycache__/formsubplottool.cpython-36.pyc,,
|
||||
matplotlib/backends/qt_editor/figureoptions.py,sha256=_TD7NJY_LzXDoljckLqKaVBYa3FSJwEFNrccYpbKMTM,9133
|
||||
matplotlib/backends/qt_editor/formlayout.py,sha256=qCAmYnZUEwgBRMVsPWuu7e1fqCU0OSM-I5Snw7ShcBI,19573
|
||||
matplotlib/backends/qt_editor/formsubplottool.py,sha256=HiiXkwCotra_hI9JU208KOs8Q9JuGH1uAW3mV5l3Evg,1934
|
||||
matplotlib/backends/tkagg.py,sha256=ro4lL5U0cLMslYmLXQRhWwR1mxlcZYGO9awistZzL1E,1319
|
||||
matplotlib/backends/web_backend/all_figures.html,sha256=GiIHkdjLO94c_GAHVX4Zk5R88uEnIUwW2CJgm3qCCv0,1512
|
||||
matplotlib/backends/web_backend/css/boilerplate.css,sha256=qui16QXRnQFNJDbcMasfH6KtN9hLjv8883U9cJmsVCE,2310
|
||||
matplotlib/backends/web_backend/css/fbm.css,sha256=Us0osu_rK8EUAdp_GXrh89tN_hUNCN-r7N1T1NvmmwI,1473
|
||||
matplotlib/backends/web_backend/css/page.css,sha256=Djf6ZNMFaM6_hVaizSkDFoqk-jn81qgduwles4AroGk,1599
|
||||
matplotlib/backends/web_backend/ipython_inline_figure.html,sha256=mzi-yWg4fcO6PdtTBCfiNuvcv04T53lcRQi-8hphwuE,1305
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_diagonals-thick_18_b81900_40x40.png,sha256=xRM8xoz-7ahUtdsImL_ZC6s8kJT2QnE04-cji0ZQfsE,418
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_diagonals-thick_20_666666_40x40.png,sha256=T9PQekCQeDFHwAxdZMQs_QzAqaqtimo0uLCotJ0xSqg,312
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_flat_10_000000_40x100.png,sha256=r1CSqnKXLAw-qLRMm5LbIAnqQWB3OLzAqdpZesaoc-o,205
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_glass_100_f6f6f6_1x400.png,sha256=_ws_N6fYaeQspm2VHS5Wm-QV4CXhlUoSqGQ-lmW3xm0,262
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_glass_100_fdf5ce_1x400.png,sha256=-VfVTyNvfByi7t0zq33BBb1mLO5iuIBvsso4Ye2ysXw,348
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_glass_65_ffffff_1x400.png,sha256=JFJf1ebnJmze7uXUNtoLcgn-dTh-bJxVxXVLIuQDGrU,207
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_gloss-wave_35_f6a828_500x100.png,sha256=Av4RPyWlnMm1at-9VqlutTAvTKW7637sDlQamg2ozNA,5815
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_highlight-soft_100_eeeeee_1x100.png,sha256=80rH2tcJybpprH1zkHIN1U_aVhUcZOc9mv9OEYavhRA,278
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_highlight-soft_75_ffe45c_1x100.png,sha256=JRY-0684rYlKOQKRQmXYJ5YiPhHS3kNPZmUVa3xi3hg,328
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_222222_256x240.png,sha256=_htyYBLdV3XU9kp9QnMKIQ8pBX6OgU8zkE05EsTZq9s,6922
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_228ef1_256x240.png,sha256=Z8eq2yeIM45jXe3uMbiKqOqQjA2MKR20tWcBTVc6bC8,4549
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_ef8c08_256x240.png,sha256=loEi-IH5oyL8PSSjAGPT549tcpGfRzIbFEaqPFhR8ck,4549
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_ffd27a_256x240.png,sha256=O-dEGxrQchuZxTdpQJQ4LEirQLT1OA5lBNYO9O8mJo8,4549
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_ffffff_256x240.png,sha256=sVicDAn7JIIW-osNHhgSqa-bSRWOtS4G94BitP_MAds,6299
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/jquery-ui.css,sha256=zs9cWf98KIv5DMYiF1a9lhJGQwhVe5LKVPJ9HNEI880,35348
|
||||
matplotlib/backends/web_backend/jquery/css/themes/base/jquery-ui.min.css,sha256=VQzrlVm7QjdSeQn_IecZgE9rnfM390H3VoIcDJljOSs,30163
|
||||
matplotlib/backends/web_backend/jquery/js/jquery-1.11.3.js,sha256=kaIBSZlozqJ4IeqPwOlDOi97qhNHWTchpUedwo5-gMU,284395
|
||||
matplotlib/backends/web_backend/jquery/js/jquery-1.11.3.min.js,sha256=7LkWEzqTdpEfELxcZZlS6wAx5Ff13zZ83lYO2_ujj7g,95957
|
||||
matplotlib/backends/web_backend/jquery/js/jquery-ui.js,sha256=DI6NdAhhFRnO2k51mumYeDShet3I8AKCQf_tf7ARNhI,470596
|
||||
matplotlib/backends/web_backend/jquery/js/jquery-ui.min.js,sha256=xNjb53_rY-WmG-4L6tTl9m6PpqknWZvRt0rO1SRnJzw,240427
|
||||
matplotlib/backends/web_backend/js/mpl.js,sha256=SqYBZRsHYoQY9d-5eCZsVpIBnlR4T7t1JiYYSZOs6Uw,16964
|
||||
matplotlib/backends/web_backend/js/mpl_tornado.js,sha256=lSxC7-yqF1GYY-6SheaHanx6SujMdcG7Vx2_3qbi-9Q,272
|
||||
matplotlib/backends/web_backend/js/nbagg_mpl.js,sha256=WfV96-6LXzUSnzCmvQ93JopqO7KPqnfbT_07Q1XJeT4,7467
|
||||
matplotlib/backends/web_backend/nbagg_uat.ipynb,sha256=y1N8hQzBJ05rJ2hZla2_Mw6tOUfNP1UHKo636W1e098,15933
|
||||
matplotlib/backends/web_backend/single_figure.html,sha256=56UAOD6qgZ-T6wE2EjGpTBG4iwAmfG3QsnX5lLQp2dI,1203
|
||||
matplotlib/backends/windowing.py,sha256=q-hSo_vvsjst6EjNkr94NIDbUucMocrMj9MSjVwkfY4,822
|
||||
matplotlib/backends/wx_compat.py,sha256=RQFVDMaMLjT8YmazLiq3BcjCcCi6xuzS_AwmxtYd2vg,968
|
||||
matplotlib/bezier.py,sha256=zwC07cDX9iyh9KWnZsPBQaFJ4CTue0NQpm1VwS2PEuM,15415
|
||||
matplotlib/blocking_input.py,sha256=E6r1m5acvx_KLDp7rZXftnA5OBtJnjy_zGWBUO9k_OA,11112
|
||||
matplotlib/category.py,sha256=AWYm1nucRySTh-6cXN2FeNYQOpwJPeUl9iFkjcr4oW0,5928
|
||||
matplotlib/cbook/__init__.py,sha256=bFrhSeVf8Ya6pNEtkqOTVABSR--ADZLbTlsLqd3BUV4,65667
|
||||
matplotlib/cbook/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/cbook/__pycache__/deprecation.cpython-36.pyc,,
|
||||
matplotlib/cbook/deprecation.py,sha256=5ghVk2E7syukLQuADs7UtHuBRJYaiuvylSymAh_axDQ,9402
|
||||
matplotlib/cm.py,sha256=MHQtLJ9UGDuyxHa-qyMxL2vikkhXB5OXhYdxUy-_68M,12843
|
||||
matplotlib/collections.py,sha256=HqR0G7ydei6YkX06MYwp0TGrVZaXz7BDNiwEPGk0Fro,66851
|
||||
matplotlib/colorbar.py,sha256=_UImtSk1sRHbLGNn_gLTy86mFYqzjovJMvioqcvn57w,59164
|
||||
matplotlib/colors.py,sha256=HeZGu5MwSkMLEABhU5qYmJw4ZbvpxeOrdV2ytY_klSg,71636
|
||||
matplotlib/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
matplotlib/compat/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/compat/__pycache__/subprocess.cpython-36.pyc,,
|
||||
matplotlib/compat/subprocess.py,sha256=-K36IW-t5SjjWcP4nL80GAE-y7v0HZE6nQ-hscwmkvg,1562
|
||||
matplotlib/container.py,sha256=Sbz7JTBAFirjClv-nSpVojuSLKIBoUo-dAve8QAFjl8,5245
|
||||
matplotlib/contour.py,sha256=XiEg_unL8WQaQf3f3c41KBHltZPXJ8Fhk2W0OGvuacc,70231
|
||||
matplotlib/dates.py,sha256=XsBhin-wZcY7FPcMrDQDBEYjYXNtmc-YbtwZiysKeqQ,61547
|
||||
matplotlib/docstring.py,sha256=M5wraMzJf7veSfzUJUArSY68bM5lkH3q6niSmW0q1xw,3787
|
||||
matplotlib/dviread.py,sha256=s_N3IU68PIVm03TFnBP8_UakGnIYwYCv1vxLDjpWsk4,37282
|
||||
matplotlib/figure.py,sha256=p9yke2KjrUkOJ5ZUt-q44GqMjCU5woMnFMAXLR3-DJk,95506
|
||||
matplotlib/font_manager.py,sha256=uWPJ8AR9gzeIljZz8baIRTDacgsAMORLgmV1lT6Ve_s,45972
|
||||
matplotlib/fontconfig_pattern.py,sha256=LYKBekGIwiHNYyYe36Lfki4JyEkZMsBinmhVzBc1wns,6593
|
||||
matplotlib/ft2font.cpython-36m-x86_64-linux-gnu.so,sha256=xKwvjhkVt1lLcRA4Swcz9iHqhWTk78UGl6CLyR5tgvw,894456
|
||||
matplotlib/gridspec.py,sha256=C5rAu1tokWgaegWBEocG6phEfemgMF9dch-HwVfKC0A,20056
|
||||
matplotlib/hatch.py,sha256=kmq09LW_AJxPY0LGJxJrrC9Yf6tPQYi209EMAveqQoE,6988
|
||||
matplotlib/image.py,sha256=LGgYY2UmfjldiGhqAU6Qwi-3I_CJAzbL6b9hFMhCWHQ,54632
|
||||
matplotlib/legend.py,sha256=M6PCguGj32SpnEuLvPhRKfWZuCofn1JYSubhRb9e5Yk,48770
|
||||
matplotlib/legend_handler.py,sha256=gmQCUHjMmvcJ4g8dSqayhaXj82jkUK36i0-4DK6ik-A,25524
|
||||
matplotlib/lines.py,sha256=uKymslFnSniquZR_5N266mXvkQJ_4eKu_sg5iP5t1rA,48503
|
||||
matplotlib/markers.py,sha256=yILg87-gXelLwbu2o1xHNdCikXVHG50frJQJ6ywY8OY,34345
|
||||
matplotlib/mathtext.py,sha256=vXlAVF9T7xA7-uRHnBu4T6LBZhr97a6obWEAr-X-mA4,121307
|
||||
matplotlib/mlab.py,sha256=mhkOI6_Wgw0aaaqPDuCE53tHLNxl8tj_ajgoCZbRUcY,124056
|
||||
matplotlib/mpl-data/fonts/afm/cmex10.afm,sha256=blR3ERmrVBV5XKkAnDCj4NMeYVgzH7cXtJ3u59u9GuE,12070
|
||||
matplotlib/mpl-data/fonts/afm/cmmi10.afm,sha256=5qwEOpedEo76bDUahyuuF1q0cD84tRrX-VQ4p3MlfBo,10416
|
||||
matplotlib/mpl-data/fonts/afm/cmr10.afm,sha256=WDvgC_D3UkGJg9u-J0U6RaT02lF4oz3lQxHtg1r3lYw,10101
|
||||
matplotlib/mpl-data/fonts/afm/cmsy10.afm,sha256=AbmzvCVWBceHRfmRfeJ9E6xzOQTFLk0U1zDfpf3_MaM,8295
|
||||
matplotlib/mpl-data/fonts/afm/cmtt10.afm,sha256=4ji7_mTpeWMa93o_UHBWPKCnqsBfhJJNllat1lJArP4,6501
|
||||
matplotlib/mpl-data/fonts/afm/pagd8a.afm,sha256=jjFrigwkTpYLqa26cpzZvKQNBo-PuF4bmDVqaM4pMWw,17183
|
||||
matplotlib/mpl-data/fonts/afm/pagdo8a.afm,sha256=sgNQdeYyx8J-itGw9h31y95aMBiTCRvmNSPTXwwS7xg,17255
|
||||
matplotlib/mpl-data/fonts/afm/pagk8a.afm,sha256=ZUtfHPloNqcvGMHMxaKDSlshhOcjwheUx143RwpGdIU,17241
|
||||
matplotlib/mpl-data/fonts/afm/pagko8a.afm,sha256=Yj1wBg6Jsqqz1KBfhRoJ3ACR-CMQol8Fj_ZM5NZ1gDk,17346
|
||||
matplotlib/mpl-data/fonts/afm/pbkd8a.afm,sha256=Zl5o6J_di9Y5j2EpHtjew-_sfg7-WoeVmO9PzOYSTUc,15157
|
||||
matplotlib/mpl-data/fonts/afm/pbkdi8a.afm,sha256=JAOno930iTyfZILMf11vWtiaTgrJcPpP6FRTRhEMMD4,15278
|
||||
matplotlib/mpl-data/fonts/afm/pbkl8a.afm,sha256=UJqJjOJ6xQDgDBLX157mKpohIJFVmHM-N6x2-DiGv14,15000
|
||||
matplotlib/mpl-data/fonts/afm/pbkli8a.afm,sha256=AWislZ2hDbs0ox_qOWREugsbS8_8lpL48LPMR40qpi0,15181
|
||||
matplotlib/mpl-data/fonts/afm/pcrb8a.afm,sha256=6j1TS2Uc7DWSc-8l42TGDc1u0Fg8JspeWfxFayjUwi8,15352
|
||||
matplotlib/mpl-data/fonts/afm/pcrbo8a.afm,sha256=smg3mjl9QaBDtQIt06ko5GvaxLsO9QtTvYANuE5hfG0,15422
|
||||
matplotlib/mpl-data/fonts/afm/pcrr8a.afm,sha256=7nxFr0Ehz4E5KG_zSE5SZOhxRH8MyfnCbw-7x5wu7tw,15339
|
||||
matplotlib/mpl-data/fonts/afm/pcrro8a.afm,sha256=NKEz7XtdFkh9cA8MvY-S3UOZlV2Y_J3tMEWFFxj7QSg,15443
|
||||
matplotlib/mpl-data/fonts/afm/phvb8a.afm,sha256=NAx4M4HjL7vANCJbc-tk04Vkol-T0oaXeQ3T2h-XUvM,17155
|
||||
matplotlib/mpl-data/fonts/afm/phvb8an.afm,sha256=8e_myD-AQkNF7q9XNLb2m76_lX2TUr3a5wog_LIE1sk,17086
|
||||
matplotlib/mpl-data/fonts/afm/phvbo8a.afm,sha256=8fkBRmJ-SWY2YrBg8fFyjJyrJp8daQ6JPO6LvhM8xPI,17230
|
||||
matplotlib/mpl-data/fonts/afm/phvbo8an.afm,sha256=aeVRvV4r15BBvxuRJ0MG8ZHuH2HViuIiCYkvuapmkmM,17195
|
||||
matplotlib/mpl-data/fonts/afm/phvl8a.afm,sha256=IyMYM-bgl-gI6rG0EuZZ2OLzlxJfGeSh8xqsh0t-eJQ,15627
|
||||
matplotlib/mpl-data/fonts/afm/phvlo8a.afm,sha256=s12C-eNnIDHJ_UVbuiprjxBjCiHIbS3Y8ORTC-qTpuI,15729
|
||||
matplotlib/mpl-data/fonts/afm/phvr8a.afm,sha256=Kt8KaRidts89EBIK29X2JomDUEDxvroeaJz_RNTi6r4,17839
|
||||
matplotlib/mpl-data/fonts/afm/phvr8an.afm,sha256=lL5fAHTRwODl-sB5mH7IfsD1tnnea4yRUK-_Ca2bQHM,17781
|
||||
matplotlib/mpl-data/fonts/afm/phvro8a.afm,sha256=3KqK3eejiR4hIFBUynuSX_4lMdE2V2T58xOF8lX-fwc,17919
|
||||
matplotlib/mpl-data/fonts/afm/phvro8an.afm,sha256=Vx9rRf3YfasMY7tz-njSxz67xHKk-fNkN7yBi0X2IP0,17877
|
||||
matplotlib/mpl-data/fonts/afm/pncb8a.afm,sha256=aoXepTcDQtQa_mspflMJkEFKefzXHoyjz6ioJVI0YNc,16028
|
||||
matplotlib/mpl-data/fonts/afm/pncbi8a.afm,sha256=pCWW1MYgy0EmvwaYsaYJaAI_LfrsKmDANHu7Pk0RaiU,17496
|
||||
matplotlib/mpl-data/fonts/afm/pncr8a.afm,sha256=0CIB2BLe9r-6_Wl5ObRTTf98UOrezmGQ8ZOuBX5kLks,16665
|
||||
matplotlib/mpl-data/fonts/afm/pncri8a.afm,sha256=5R-pLZOnaHNG8pjV6MP3Ai-d2OTQYR_cYCb5zQhzfSU,16920
|
||||
matplotlib/mpl-data/fonts/afm/pplb8a.afm,sha256=3EzUbNnXr5Ft5eFLY00W9oWu59rHORgDXUuJaOoKN58,15662
|
||||
matplotlib/mpl-data/fonts/afm/pplbi8a.afm,sha256=X_9tVspvrcMer3OS8qvdwjFFqpAXYZneyCL2NHA902g,15810
|
||||
matplotlib/mpl-data/fonts/afm/pplr8a.afm,sha256=ijMb497FDJ9nVdVMb21F7W3-cu9sb_9nF0oriFpSn8k,15752
|
||||
matplotlib/mpl-data/fonts/afm/pplri8a.afm,sha256=8KITbarcUUMi_hdoRLLmNHtlqs0TtOSKqtPFft7X5nY,15733
|
||||
matplotlib/mpl-data/fonts/afm/psyr.afm,sha256=Iyt8ajE4B2Tm34oBj2pKtctIf9kPfq05suQefq8p3Ro,9644
|
||||
matplotlib/mpl-data/fonts/afm/ptmb8a.afm,sha256=bL1fA1NC4_nW14Zrnxz4nHlXJb4dzELJPvodqKnYeMg,17983
|
||||
matplotlib/mpl-data/fonts/afm/ptmbi8a.afm,sha256=-_Ui6XlKaFTHEnkoS_-1GtIr5VtGa3gFQ2ezLOYHs08,18070
|
||||
matplotlib/mpl-data/fonts/afm/ptmr8a.afm,sha256=IEcsWcmzJyjCwkgsw4o6hIMmzlyXUglJat9s1PZNnEU,17942
|
||||
matplotlib/mpl-data/fonts/afm/ptmri8a.afm,sha256=49fQMg5fIGguZ7rgc_2styMK55Pv5bPTs7wCzqpcGpk,18068
|
||||
matplotlib/mpl-data/fonts/afm/putb8a.afm,sha256=qMaHTdpkrNL-m4DWhjpxJCSmgYkCv1qIzLlFfM0rl40,21532
|
||||
matplotlib/mpl-data/fonts/afm/putbi8a.afm,sha256=g7AVJyiTxeMpNk_1cSfmYgM09uNUfPlZyWGv3D1vcAk,21931
|
||||
matplotlib/mpl-data/fonts/afm/putr8a.afm,sha256=XYmNC5GQgSVAZKTIYdYeNksE6znNm9GF_0SmQlriqx0,22148
|
||||
matplotlib/mpl-data/fonts/afm/putri8a.afm,sha256=i7fVe-iLyLtQxCfAa4IxdxH-ufcHmMk7hbCGG5TxAY4,21891
|
||||
matplotlib/mpl-data/fonts/afm/pzcmi8a.afm,sha256=wyuoIWEZOcoXrSl1tPzLkEahik7kGi91JJj-tkFRG4A,16250
|
||||
matplotlib/mpl-data/fonts/afm/pzdr.afm,sha256=MyjLAnzKYRdQBfof1W3k_hf30MvqOkqL__G22mQ5xww,9467
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Bold.afm,sha256=sIDDI-B82VZ3C0mI_mHFITCZ7PVn37AIYMv1CrHX4sE,15333
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Courier-BoldOblique.afm,sha256=zg61QobD3YU9UBfCXmvmhBNaFKno-xj8sY0b2RpgfLw,15399
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Oblique.afm,sha256=vRQm5j1sTUN4hicT1PcVZ9P9DTTUHhEzfPXqUUzVZhE,15441
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Courier.afm,sha256=Mdcq2teZEBJrIqVXnsnhee7oZnTs6-P8_292kWGTrw4,15335
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Bold.afm,sha256=i2l4gcjuYXoXf28uK7yIVwuf0rnw6J7PwPVQeHj5iPw,69269
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-BoldOblique.afm,sha256=Um5O6qK11DXLt8uj_0IoWkc84TKqHK3bObSKUswQqvY,69365
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Oblique.afm,sha256=hVYDg2b52kqtbVeCzmiv25bW1yYdpkZS-LXlGREN2Rs,74392
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica.afm,sha256=23cvKDD7bQAJB3kdjSahJSTZaUOppznlIO6FXGslyW8,74292
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Symbol.afm,sha256=P5UaoXr4y0qh4SiMa5uqijDT6ZDr2-jPmj1ayry593E,9740
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Times-Bold.afm,sha256=cQTmr2LFPwKQE_sGQageMcmFicjye16mKJslsJLHQyE,64251
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Times-BoldItalic.afm,sha256=pzWOdycm6RqocBWgAVY5Jq0z3Fp7LuqWgLNMx4q6OFw,59642
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Times-Italic.afm,sha256=bK5puSMpGT_YUILwyJrXoxjfj7XJOdfv5TQ_iKsJRzw,66328
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/Times-Roman.afm,sha256=hhNrUnpazuDDKD1WpraPxqPWCYLrO7D7bMVOg-zI13o,60460
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/ZapfDingbats.afm,sha256=ZuOmt9GcKofjdOq8kqhPhtAIhOwkL2rTJTmZxAjFakA,9527
|
||||
matplotlib/mpl-data/fonts/pdfcorefonts/readme.txt,sha256=MRv8ppSITYYAb7lt5EOw9DWWNZIblfxsFhu5TQE7cpI,828
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf,sha256=sYS4njwQdfIva3FXW2_CDUlys8_TsjMiym_Vltyu8Wc,704128
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf,sha256=bt8CgxYBhq9FHL7nHnuEXy5Mq_Jku5ks5mjIPCVGXm8,641720
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf,sha256=zN90s1DxH9PdV3TeUOXmNGoaXaH1t9X7g1kGZel6UhM,633840
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf,sha256=P99pyr8GBJ6nCgC1kZNA4s4ebQKwzDxLRPtoAb0eDSI,756072
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf,sha256=ggmdz7paqGjN_CdFGYlSX-MpL3N_s8ngMozpzvWWUvY,25712
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf,sha256=uq2ppRcv4giGJRr_BDP8OEYZEtXa8HKH577lZiCo2pY,331536
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf,sha256=ppCBwVx2yCfgonpaf1x0thNchDSZlVSV_6jCDTqYKIs,253116
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf,sha256=KAUoE_enCfyJ9S0ZLcmV708P3Fw9e3OknWhJsZFtDNA,251472
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf,sha256=YC7Ia4lIz82VZIL-ZPlMNshndwFJ7y95HUYT9EO87LM,340240
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf,sha256=w3U_Lta8Zz8VhG3EWt2-s7nIcvMvsY_VOiHxvvHtdnY,355692
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf,sha256=2T7-x6nS6CZ2jRou6VuVhw4V4pWZqE80hK8d4c7C4YE,347064
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf,sha256=PnmU-8VPoQzjNSpC1Uj63X2crbacsRCbydlg9trFfwQ,345612
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf,sha256=EHJElW6ZYrnpb6zNxVGCXgrgiYrhNzcTPhuSGi_TX_o,379740
|
||||
matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf,sha256=KRTzLkfHd8J75Wd6-ufbTeefnkXeb8kJfZlJwjwU99U,14300
|
||||
matplotlib/mpl-data/fonts/ttf/LICENSE_DEJAVU,sha256=11k43sCY8G8Kw8AIUwZdlPAgvhw8Yu8dwpdboVtNmw4,4816
|
||||
matplotlib/mpl-data/fonts/ttf/LICENSE_STIX,sha256=cxFOZdp1AxNhXR6XxCzf5iJpNcu-APm-geOHhD-s0h8,5475
|
||||
matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf,sha256=FnN4Ax4t3cYhbWeBnJJg6aBv_ExHjk4jy5im_USxg8I,448228
|
||||
matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf,sha256=6FM9xwg_o0a9oZM9YOpKg7Z9CUW86vGzVB-CtKDixqA,237360
|
||||
matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf,sha256=mHiP1LpI37sr0CbA4gokeosGxzcoeWKLemuw1bsJc2w,181152
|
||||
matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf,sha256=bPyzM9IrfDxiO9_UAXTxTIXD1nMcphZsHtyAFA6uhSc,175040
|
||||
matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf,sha256=Ulb34CEzWsSFTRgPDovxmJZOwvyCAXYnbhaqvGU3u1c,59108
|
||||
matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf,sha256=XRBqW3jR_8MBdFU0ObhiV7-kXwiBIMs7QVClHcT5tgs,30512
|
||||
matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf,sha256=pb22DnbDf2yQqizotc3wBDqFGC_g27YcCGJivH9-Le8,41272
|
||||
matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf,sha256=BMr9pWiBv2YIZdq04X4c3CgL6NPLUPrl64aV1N4w9Ug,46752
|
||||
matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf,sha256=wYuH1gYUpCuusqItRH5kf9p_s6mUD-9X3L5RvRtKSxs,13656
|
||||
matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf,sha256=yNdvjUoSmsZCULmD7SVq9HabndG9P4dPhboL1JpAf0s,12228
|
||||
matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf,sha256=-9xVMYL4_1rcO8FiCKrCfR4PaSmKtA42ddLGqwtei1w,15972
|
||||
matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf,sha256=cYexyo8rZcdqMlpa9fNF5a2IoXLUTZuIvh0JD1Qp0i4,12556
|
||||
matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf,sha256=0lbHzpndzJmO8S42mlkhsz5NbvJLQCaH5Mcc7QZRDzc,19760
|
||||
matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf,sha256=3eBc-VtYbhQU3BnxiypfO6eAzEu8BdDvtIJSFbkS2oY,12192
|
||||
matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf,sha256=XFSKCptbESM8uxHtUFSAV2cybwxhSjd8dWVByq6f3w0,15836
|
||||
matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf,sha256=MUCYHrA0ZqFiSE_PjIGlJZgMuv79aUgQqE7Dtu3kuo0,12116
|
||||
matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf,sha256=_sdxDuEwBDtADpu9CyIXQxV7sIqA2TZVBCUiUjq5UCk,15704
|
||||
matplotlib/mpl-data/fonts/ttf/cmb10.ttf,sha256=B0SXtQxD6ldZcYFZH5iT04_BKofpUQT1ZX_CSB9hojo,25680
|
||||
matplotlib/mpl-data/fonts/ttf/cmex10.ttf,sha256=ryjwwXByOsd2pxv6WVrKCemNFa5cPVTOGa_VYZyWqQU,21092
|
||||
matplotlib/mpl-data/fonts/ttf/cmmi10.ttf,sha256=MJKWW4gR_WpnZXmWZIRRgfwd0TMLk3-RWAjEhdMWI00,32560
|
||||
matplotlib/mpl-data/fonts/ttf/cmr10.ttf,sha256=Tdl2GwWMAJ25shRfVe5mF9CTwnPdPWxbPkP_YRD6m_Y,26348
|
||||
matplotlib/mpl-data/fonts/ttf/cmss10.ttf,sha256=ffkag9BbLkcexjjLC0NaNgo8eSsJ_EKn2mfpHy55EVo,20376
|
||||
matplotlib/mpl-data/fonts/ttf/cmsy10.ttf,sha256=uyJu2TLz8QDNDlL15JEu5VO0G2nnv9uNOFTbDrZgUjI,29396
|
||||
matplotlib/mpl-data/fonts/ttf/cmtt10.ttf,sha256=YhHwmuk1mZka_alwwkZp2tGnfiU9kVYk-_IS9wLwcdc,28136
|
||||
matplotlib/mpl-data/fonts/ttf/local.conf,sha256=wcg11V6oGPKn4c43Z_bGGo9TCyvjMWq9msuzMmdtRTo,988
|
||||
matplotlib/mpl-data/images/back.gif,sha256=sdkxFRAh-Mgs44DTvruO5OxcI3Av9CS1g5MqMA_DDkQ,608
|
||||
matplotlib/mpl-data/images/back.pdf,sha256=ZR7CJo_dAeCM-KlaGvskgtHQyRtrPIolc8REOmcoqJk,1623
|
||||
matplotlib/mpl-data/images/back.png,sha256=E4dGf4Gnz1xJ1v2tMygHV0YNQgShreDeVApaMb-74mU,380
|
||||
matplotlib/mpl-data/images/back.svg,sha256=yRdMiKsa-awUm2x_JE_rEV20rNTa7FInbFBEoMo-6ik,1512
|
||||
matplotlib/mpl-data/images/back_large.gif,sha256=tqCtecrxNrPuDCUj7FGs8UXWftljKcwgp5cSBBhXwiQ,799
|
||||
matplotlib/mpl-data/images/back_large.png,sha256=9A6hUSQeszhYONE4ZuH3kvOItM0JfDVu6tkfromCbsQ,620
|
||||
matplotlib/mpl-data/images/filesave.gif,sha256=wAyNwOPd9c-EIPwcUAlqHSfLmxq167nhDVppOWPy9UA,723
|
||||
matplotlib/mpl-data/images/filesave.pdf,sha256=P1EPPV2g50WTt8UaX-6kFoTZM1xVqo6S2H6FJ6Zd1ec,1734
|
||||
matplotlib/mpl-data/images/filesave.png,sha256=b7ctucrM_F2mG-DycTedG_a_y4pHkx3F-zM7l18GLhk,458
|
||||
matplotlib/mpl-data/images/filesave.svg,sha256=oxPVbLS9Pzelz71C1GCJWB34DZ0sx_pUVPRHBrCZrGs,2029
|
||||
matplotlib/mpl-data/images/filesave_large.gif,sha256=IXrenlwu3wwO8WTRvxHt_q62NF6ZWyqk3jZhm6GE-G8,1498
|
||||
matplotlib/mpl-data/images/filesave_large.png,sha256=LNbRD5KZ3Kf7nbp-stx_a1_6XfGBSWUfDdpgmnzoRvk,720
|
||||
matplotlib/mpl-data/images/forward.gif,sha256=VNL9R-dECOX7wUAYPtU_DWn5hwi3SwLR17DhmBvUIxE,590
|
||||
matplotlib/mpl-data/images/forward.pdf,sha256=KIqIL4YId43LkcOxV_TT5uvz1SP8k5iUNUeJmAElMV8,1630
|
||||
matplotlib/mpl-data/images/forward.png,sha256=pKbLepgGiGeyY2TCBl8svjvm7Z4CS3iysFxcq4GR-wk,357
|
||||
matplotlib/mpl-data/images/forward.svg,sha256=NnQDOenfjsn-o0aJMUfErrP320Zcx9XHZkLh0cjMHsk,1531
|
||||
matplotlib/mpl-data/images/forward_large.gif,sha256=H6Jbcc7qJwHJAE294YqI5Bm-5irofX40cKRvYdrG_Ig,786
|
||||
matplotlib/mpl-data/images/forward_large.png,sha256=36h7m7DZDHql6kkdpNPckyi2LKCe_xhhyavWARz_2kQ,593
|
||||
matplotlib/mpl-data/images/hand.gif,sha256=3lRfmAqQU7A2t1YXXsB9IbwzK7FaRh-IZO84D5-xCrw,1267
|
||||
matplotlib/mpl-data/images/hand.pdf,sha256=hspwkNY915KPD7AMWnVQs7LFPOtlcj0VUiLu76dMabQ,4172
|
||||
matplotlib/mpl-data/images/hand.png,sha256=2cchRETGKa0hYNKUxnJABwkyYXEBPqJy_VqSPlT0W2Q,979
|
||||
matplotlib/mpl-data/images/hand.svg,sha256=tsVIES_nINrAbH4FqdsCGOx0SVE37vcofSYBhnnaOP0,4888
|
||||
matplotlib/mpl-data/images/hand_large.gif,sha256=H5IHmVTvOqHQb9FZ_7g7AlPt9gv-zRq0L5_Q9B7OuvU,973
|
||||
matplotlib/mpl-data/images/help.pdf,sha256=CeE978IMi0YWznWKjIT1R8IrP4KhZ0S7usPUvreSgcA,1813
|
||||
matplotlib/mpl-data/images/help.png,sha256=s4pQrqaQ0py8I7vc9hv3BI3DO_tky-7YBMpaHuBDCBY,472
|
||||
matplotlib/mpl-data/images/help.ppm,sha256=mVPvgwcddzCM-nGZd8Lnl_CorzDkRIXQE17b7qo8vlU,1741
|
||||
matplotlib/mpl-data/images/help.svg,sha256=KXabvQhqIWen_t2SvZuddFYa3S0iI3W8cAKm3s1fI8Q,1870
|
||||
matplotlib/mpl-data/images/help_large.png,sha256=1IwEyWfGRgnoCWM-r9CJHEogTJVD5n1c8LXTK4AJ4RE,747
|
||||
matplotlib/mpl-data/images/help_large.ppm,sha256=MiCSKp1Su88FXOi9MTtkQDA2srwbX3w5navi6cneAi4,6925
|
||||
matplotlib/mpl-data/images/home.gif,sha256=NKuFM7tTtFngdfsOpJ4AxYTL8PYS5GWKAoiJjBMwLlU,666
|
||||
matplotlib/mpl-data/images/home.pdf,sha256=e0e0pI-XRtPmvUCW2VTKL1DeYu1pvPmUUeRSgEbWmik,1737
|
||||
matplotlib/mpl-data/images/home.png,sha256=IcFdAAUa6_A0qt8IO3I8p4rpXpQgAlJ8ndBECCh7C1w,468
|
||||
matplotlib/mpl-data/images/home.svg,sha256=n_AosjJVXET3McymFuHgXbUr5vMLdXK2PDgghX8Cch4,1891
|
||||
matplotlib/mpl-data/images/home_large.gif,sha256=k86PJCgED46sCFkOlUYHA0s5U7OjRsc517bpAtU2JSw,1422
|
||||
matplotlib/mpl-data/images/home_large.png,sha256=uxS2O3tWOHh1iau7CaVV4ermIJaZ007ibm5Z3i8kXYg,790
|
||||
matplotlib/mpl-data/images/matplotlib.pdf,sha256=BkSUf-2xoij-eXfpV2t7y1JFKG1zD1gtV6aAg3Xi_wE,22852
|
||||
matplotlib/mpl-data/images/matplotlib.png,sha256=w8KLRYVa-voUZXa41hgJauQuoois23f3NFfdc72pUYY,1283
|
||||
matplotlib/mpl-data/images/matplotlib.ppm,sha256=voTyfqUGvirNbkrsJGMwJT0z0g_KFWAnO8Hn8J4XXh8,1741
|
||||
matplotlib/mpl-data/images/matplotlib.svg,sha256=QiTIcqlQwGaVPtHsEk-vtmJk1wxwZSvijhqBe_b9VCI,62087
|
||||
matplotlib/mpl-data/images/matplotlib_large.png,sha256=ElRoue9grUqkZXJngk-nvh4GKfpvJ4gE69WryjCbX5U,3088
|
||||
matplotlib/mpl-data/images/move.gif,sha256=FN52MptH4FZiwmV2rQgYCO2FvO3m5LtqYv8jk6Xbeyk,679
|
||||
matplotlib/mpl-data/images/move.pdf,sha256=CXk3PGK9WL5t-5J-G2X5Tl-nb6lcErTBS5oUj2St6aU,1867
|
||||
matplotlib/mpl-data/images/move.png,sha256=TmjR41IzSzxGbhiUcV64X0zx2BjrxbWH3cSKvnG2vzc,481
|
||||
matplotlib/mpl-data/images/move.svg,sha256=_ZKpcwGD6DMTkZlbyj0nQbT8Ygt5vslEZ0OqXaXGd4E,2509
|
||||
matplotlib/mpl-data/images/move_large.gif,sha256=RMIAr-G9OOY7vWC04oN6qv5TAHJxhQGhLsw_bNsvWbg,951
|
||||
matplotlib/mpl-data/images/move_large.png,sha256=Skjz2nW_RTA5s_0g88gdq2hrVbm6DOcfYW4Fu42Fn9U,767
|
||||
matplotlib/mpl-data/images/qt4_editor_options.pdf,sha256=2qu6GVyBrJvVHxychQoJUiXPYxBylbH2j90QnytXs_w,1568
|
||||
matplotlib/mpl-data/images/qt4_editor_options.png,sha256=EryQjQ5hh2dwmIxtzCFiMN1U6Tnd11p1CDfgH5ZHjNM,380
|
||||
matplotlib/mpl-data/images/qt4_editor_options.svg,sha256=E00YoX7u4NrxMHm_L1TM8PDJ88bX5qRdCrO-Uj59CEA,1244
|
||||
matplotlib/mpl-data/images/qt4_editor_options_large.png,sha256=-Pd-9Vh5aIr3PZa8O6Ge_BLo41kiEnpmkdDj8a11JkY,619
|
||||
matplotlib/mpl-data/images/subplots.gif,sha256=QfhmUdcrko08-WtrzCJUjrVFDTvUZCJEXpARNtzEwkg,691
|
||||
matplotlib/mpl-data/images/subplots.pdf,sha256=Q0syPMI5EvtgM-CE-YXKOkL9eFUAZnj_X2Ihoj6R4p4,1714
|
||||
matplotlib/mpl-data/images/subplots.png,sha256=MUfCItq3_yzb9yRieGOglpn0Y74h8IA7m5i70B63iRc,445
|
||||
matplotlib/mpl-data/images/subplots.svg,sha256=8acBogXIr9OWGn1iD6mUkgahdFZgDybww385zLCLoIs,2130
|
||||
matplotlib/mpl-data/images/subplots_large.gif,sha256=Ff3ERmtVAaGP9i1QGUNnIIKac6LGuSW2Qf4DrockZSI,1350
|
||||
matplotlib/mpl-data/images/subplots_large.png,sha256=Edu9SwVMQEXJZ5ogU5cyW7VLcwXJdhdf-EtxxmxdkIs,662
|
||||
matplotlib/mpl-data/images/zoom_to_rect.gif,sha256=mTX6h9fh2W9zmvUYqeibK0TZ7qIMKOB1nAXMpD_jDys,696
|
||||
matplotlib/mpl-data/images/zoom_to_rect.pdf,sha256=SEvPc24gfZRpl-dHv7nx8KkxPyU66Kq4zgQTvGFm9KA,1609
|
||||
matplotlib/mpl-data/images/zoom_to_rect.png,sha256=aNz3QZBrIgxu9E-fFfaQweCVNitGuDUFoC27e5NU2L4,530
|
||||
matplotlib/mpl-data/images/zoom_to_rect.svg,sha256=1vRxr3cl8QTwTuRlQzD1jxu0fXZofTJ2PMgG97E7Bco,1479
|
||||
matplotlib/mpl-data/images/zoom_to_rect_large.gif,sha256=nx5LUpTAH6ZynM3ZfZDS-wR87jbMUsUnyQ27NGkV0_c,1456
|
||||
matplotlib/mpl-data/images/zoom_to_rect_large.png,sha256=V6pkxmm6VwFExdg_PEJWdK37HB7k3cE_corLa7RbUMk,1016
|
||||
matplotlib/mpl-data/matplotlibrc,sha256=-CfOXSRZ7WaduxxmjJjX-_PLETPwuzleTpGhTLd7ylU,33061
|
||||
matplotlib/mpl-data/sample_data/Minduka_Present_Blue_Pack.png,sha256=XnKGiCanpDKalQ5anvo5NZSAeDP7fyflzQAaivuc0IE,13634
|
||||
matplotlib/mpl-data/sample_data/None_vs_nearest-pdf.png,sha256=5CPvcG3SDNfOXx39CMKHCNS9JKZ-fmOUwIfpppNXsQ0,106228
|
||||
matplotlib/mpl-data/sample_data/README.txt,sha256=ABz19VBKfGewdY39QInG9Qccgn1MTYV3bT5Ph7TCy2Y,128
|
||||
matplotlib/mpl-data/sample_data/aapl.npz,sha256=GssVYka_EccteiXbNRJJ5GsuqU7G8F597qX7srYXZsw,107503
|
||||
matplotlib/mpl-data/sample_data/ada.png,sha256=X1hjJK1_1Nc8DN-EEhey3G7Sq8jBwQDKNSl4cCAE0uY,308313
|
||||
matplotlib/mpl-data/sample_data/axes_grid/bivariate_normal.npy,sha256=DpWZ9udAh6ospYqneEa27D6EkRgORFwHosacZXVu98U,1880
|
||||
matplotlib/mpl-data/sample_data/ct.raw.gz,sha256=LDvvgH-mycRQF2D29-w5MW94ZI0opvwKUoFI8euNpMk,256159
|
||||
matplotlib/mpl-data/sample_data/data_x_x2_x3.csv,sha256=A0SU3buOUGhT-NI_6LQ6p70fFSIU3iLFdgzvzrKR6SE,132
|
||||
matplotlib/mpl-data/sample_data/demodata.csv,sha256=MRybziqnyrqMCH9qG7Mr6BwcohIhftVG5dejXV2AX2M,659
|
||||
matplotlib/mpl-data/sample_data/eeg.dat,sha256=KGVjFt8ABKz7p6XZirNfcxSTOpGGNuyA8JYErRKLRBc,25600
|
||||
matplotlib/mpl-data/sample_data/embedding_in_wx3.xrc,sha256=cUqVw5vDHNSZoaO4J0ebZUf5SrJP36775abs7R9Bclg,2186
|
||||
matplotlib/mpl-data/sample_data/goog.npz,sha256=QAkXzzDmtmT3sNqT18dFhg06qQCNqLfxYNLdEuajGLE,22845
|
||||
matplotlib/mpl-data/sample_data/grace_hopper.jpg,sha256=qMptc0dlcDsJcoq0f-WfRz2Trjln_CTHwCiMPHrbcTA,61306
|
||||
matplotlib/mpl-data/sample_data/grace_hopper.png,sha256=MCf0ju2kpC40srQ0xw4HEyOoKhLL4khP3jHfU9_dR7s,628280
|
||||
matplotlib/mpl-data/sample_data/jacksboro_fault_dem.npz,sha256=1JP1CjPoKkQgSUxU0fyhU50Xe9wnqxkLxf5ukvYvtjc,174061
|
||||
matplotlib/mpl-data/sample_data/logo2.png,sha256=ITxkJUsan2oqXgJDy6DJvwJ4aHviKeWGnxPkTjXUt7A,33541
|
||||
matplotlib/mpl-data/sample_data/membrane.dat,sha256=q3lbQpIBpbtXXGNw1eFwkN_PwxdDGqk4L46IE2b0M1c,48000
|
||||
matplotlib/mpl-data/sample_data/msft.csv,sha256=GArKb0O3DgKZRsKdJf6lX3rMSf-PCekIiBoLNdgF7Mk,3211
|
||||
matplotlib/mpl-data/sample_data/percent_bachelors_degrees_women_usa.csv,sha256=TzoqamsV_N3d3lW7SKmj14zZVX4FOOg9jJcsC5U9pbA,5681
|
||||
matplotlib/mpl-data/sample_data/s1045.ima.gz,sha256=MrQk1k9it-ccsk0p_VOTitVmTWCAVaZ6srKvQ2n4uJ4,33229
|
||||
matplotlib/mpl-data/stylelib/Solarize_Light2.mplstyle,sha256=PECeO60wwJe2sSDvxapBJRuKGek0qLcoaN8qOX6tgNQ,1255
|
||||
matplotlib/mpl-data/stylelib/_classic_test.mplstyle,sha256=XnegNNz-4tr8vnTgI1IakyHYPryuInJD1GidF9a8n6E,25458
|
||||
matplotlib/mpl-data/stylelib/bmh.mplstyle,sha256=-KbhaI859BITHIoyUZIfpQDjfckgLAlDAS_ydKsm6mc,712
|
||||
matplotlib/mpl-data/stylelib/classic.mplstyle,sha256=brJE6RZ114bTIVwl7yBm2sd6s0TyRrUq9t-qi--Ih20,25639
|
||||
matplotlib/mpl-data/stylelib/dark_background.mplstyle,sha256=-EGmoFm_35Zk7oRp29UalT56HsOSuJbYMeQGdAATnz4,477
|
||||
matplotlib/mpl-data/stylelib/fast.mplstyle,sha256=yTa2YEIIP9xi5V_G0p2vSlxghuhNwjRi9gPECMxyRiM,288
|
||||
matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle,sha256=WNUmAFuBPcqQPVgt6AS1ldy8Be2XO01N-1YQL__Q6ZY,832
|
||||
matplotlib/mpl-data/stylelib/ggplot.mplstyle,sha256=xhjLwr8hiikEXKy8APMy0Bmvtz1g0WnG84gX7e9lArs,957
|
||||
matplotlib/mpl-data/stylelib/grayscale.mplstyle,sha256=KCLg-pXpns9cnKDXKN2WH6mV41OH-6cbT-5zKQotSdw,526
|
||||
matplotlib/mpl-data/stylelib/seaborn-bright.mplstyle,sha256=pDqn3-NUyVLvlfkYs8n8HzNZvmslVMChkeH-HtZuJIc,144
|
||||
matplotlib/mpl-data/stylelib/seaborn-colorblind.mplstyle,sha256=eCSzFj5_2vR6n5qu1rHE46wvSVGZcdVqz85ov40ZsH8,148
|
||||
matplotlib/mpl-data/stylelib/seaborn-dark-palette.mplstyle,sha256=p5ABKNQHRG7bk4HXqMQrRBjDlxGAo3RCXHdQmP7g-Ng,142
|
||||
matplotlib/mpl-data/stylelib/seaborn-dark.mplstyle,sha256=I4xQ75vE5_9X4k0cNDiqhhnF3OcrZ2xlPX8Ll7OCkoE,667
|
||||
matplotlib/mpl-data/stylelib/seaborn-darkgrid.mplstyle,sha256=2bXOSzS5gmPzRBrRmzVWyhg_7ZaBRQ6t_-O-cRuyZoA,670
|
||||
matplotlib/mpl-data/stylelib/seaborn-deep.mplstyle,sha256=44dLcXjjRgR-6yaopgGRInaVgz3jk8VJVQTbBIcxRB0,142
|
||||
matplotlib/mpl-data/stylelib/seaborn-muted.mplstyle,sha256=T4o3jvqKD_ImXDkp66XFOV_xrBVFUolJU34JDFk1Xkk,143
|
||||
matplotlib/mpl-data/stylelib/seaborn-notebook.mplstyle,sha256=PcvZQbYrDdducrNlavBPmQ1g2minio_9GkUUFRdgtoM,382
|
||||
matplotlib/mpl-data/stylelib/seaborn-paper.mplstyle,sha256=n0mboUp2C4Usq2j6tNWcu4TZ_YT4-kKgrYO0t-rz1yw,393
|
||||
matplotlib/mpl-data/stylelib/seaborn-pastel.mplstyle,sha256=8nV8qRpbUrnFZeyE6VcQ1oRuZPLil2W74M2U37DNMOE,144
|
||||
matplotlib/mpl-data/stylelib/seaborn-poster.mplstyle,sha256=dUaKqTE4MRfUq2rWVXbbou7kzD7Z9PE9Ko8aXLza8JA,403
|
||||
matplotlib/mpl-data/stylelib/seaborn-talk.mplstyle,sha256=7FnBaBEdWBbncTm6_ER-EQVa_bZgU7dncgez-ez8R74,403
|
||||
matplotlib/mpl-data/stylelib/seaborn-ticks.mplstyle,sha256=CITZmZFUFp40MK2Oz8tI8a7WRoCizQU9Z4J172YWfWw,665
|
||||
matplotlib/mpl-data/stylelib/seaborn-white.mplstyle,sha256=WjJ6LEU6rlCwUugToawciAbKP9oERFHr9rfFlUrdTx0,665
|
||||
matplotlib/mpl-data/stylelib/seaborn-whitegrid.mplstyle,sha256=ec4BjsNzmOvHptcJ3mdPxULF3S1_U1EUocuqfIpw-Nk,664
|
||||
matplotlib/mpl-data/stylelib/seaborn.mplstyle,sha256=_Xu6qXKzi4b3GymCOB1b1-ykKTQ8xhDliZ8ezHGTiAs,1130
|
||||
matplotlib/mpl-data/stylelib/tableau-colorblind10.mplstyle,sha256=BsirZVd1LmPWT4tBIz6loZPjZcInoQrIGfC7rvzqmJw,190
|
||||
matplotlib/offsetbox.py,sha256=KZdfpLd6rYRjmRAboaRBuKag8lDUCzMEeykgmp4irx8,55449
|
||||
matplotlib/patches.py,sha256=wUAZISlGrUWm_bxDh88ryGoWkOXrJZttyanOnZWdDQ4,151034
|
||||
matplotlib/path.py,sha256=Ys7EYJOyCTAQm0fROCvHdgg2Kxp5AZwkJhG42hw3QZc,37139
|
||||
matplotlib/patheffects.py,sha256=l7huZDCPG8RS3LLq67EztQg7s64zngoUN1R60zxpp5k,14139
|
||||
matplotlib/projections/__init__.py,sha256=-_LPKYaHOdfgxYdCtPs-nDJvjQ8ZTXIuAcC0v_1_zFY,2874
|
||||
matplotlib/projections/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/projections/__pycache__/geo.cpython-36.pyc,,
|
||||
matplotlib/projections/__pycache__/polar.cpython-36.pyc,,
|
||||
matplotlib/projections/geo.py,sha256=EI7YSw7UbZTsb31dCTAH0aJRXQct-cOnm45alvAj3Cc,18909
|
||||
matplotlib/projections/polar.py,sha256=6X8t4p4LAx8h7O7WJ-T5SDhbKaNsA8X6dcXIl4z4wgM,55006
|
||||
matplotlib/pylab.py,sha256=lNqCpuoTGfjprzltmvpQ3DYthdj_FJf6PRFfeEv3HhY,10331
|
||||
matplotlib/pyplot.py,sha256=5AwtjpRSBFNyRyKTVrWIWSl_EPVPyPQ6RpZdOidI1nI,110436
|
||||
matplotlib/quiver.py,sha256=9eP6xfF6pOOQpQ3zxbvedZewUYE236OS9gwRNZhPvws,45910
|
||||
matplotlib/rcsetup.py,sha256=aFy_3zYioc0oCEmRdvqIGlCRbSA6hj7BKKTO-k2__Gw,58203
|
||||
matplotlib/sankey.py,sha256=tMlZvy0CmG55hpDVY6Dqk461x6vvSxvhDmkIXJG_qDo,38734
|
||||
matplotlib/scale.py,sha256=l9r0dmYGeMCLkqcrcJRIBFj6tJq6Znvc9ZoRTynHLoI,19164
|
||||
matplotlib/sphinxext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
matplotlib/sphinxext/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/sphinxext/__pycache__/mathmpl.cpython-36.pyc,,
|
||||
matplotlib/sphinxext/__pycache__/plot_directive.cpython-36.pyc,,
|
||||
matplotlib/sphinxext/mathmpl.py,sha256=sdWYQ5aBB9hGiEY5E3j9By-DaFENDNHtTHxLMRQXrWc,3919
|
||||
matplotlib/sphinxext/plot_directive.py,sha256=uMBuVvlmjuLUr6r1iDm42L1yN7A1r_knUXrcfxPtTBU,27082
|
||||
matplotlib/sphinxext/tests/__init__.py,sha256=gTnBimTaMh3t3WC49SvyeSH0rsbWxQgjAsr5H8HRDig,23
|
||||
matplotlib/sphinxext/tests/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/sphinxext/tests/__pycache__/conftest.cpython-36.pyc,,
|
||||
matplotlib/sphinxext/tests/__pycache__/test_tinypages.cpython-36.pyc,,
|
||||
matplotlib/sphinxext/tests/conftest.py,sha256=Ph6QZKdfAnkPwU52StddC-uwtCHfANKX1dDXgtX122g,213
|
||||
matplotlib/sphinxext/tests/test_tinypages.py,sha256=uU72lyon36bSL2xF_90zfkblEolK_Cj5VGrfEpv0_wU,2058
|
||||
matplotlib/sphinxext/tests/tinypages/__pycache__/conf.cpython-36.pyc,,
|
||||
matplotlib/sphinxext/tests/tinypages/__pycache__/range4.cpython-36.pyc,,
|
||||
matplotlib/sphinxext/tests/tinypages/__pycache__/range6.cpython-36.pyc,,
|
||||
matplotlib/sphinxext/tests/tinypages/_static/README.txt,sha256=1nnoizmUuHn5GKx8RL6MwJPlkyGmu_KHhYIMTDSWUNM,303
|
||||
matplotlib/sphinxext/tests/tinypages/conf.py,sha256=0_a4wyqPA9oaOFpLLpSEzkZI-hwtyRbqLWBx9nf0sLA,8432
|
||||
matplotlib/sphinxext/tests/tinypages/index.rst,sha256=kLSy7c3SoIAVsKOFkbzB4zFVzk3HW3d_rJHlHcNGBAg,446
|
||||
matplotlib/sphinxext/tests/tinypages/range4.py,sha256=fs2krzi9sY9ysmJRQCdGs_Jh1L9vDXDrNse7c0aX_Rw,81
|
||||
matplotlib/sphinxext/tests/tinypages/range6.py,sha256=a2EaHrNwXz4GJqhRuc7luqRpt7sqLKhTKeid9Drt2QQ,281
|
||||
matplotlib/sphinxext/tests/tinypages/some_plots.rst,sha256=C9rwV9UVlhFvxm8VqV6PoAP1AQ8Kk0LGZI9va4joif0,2156
|
||||
matplotlib/spines.py,sha256=dctV_-aWkJuc52EgZof2aPjcelWVKOXttz66sZyrPkk,21217
|
||||
matplotlib/stackplot.py,sha256=4YbKAU349muVPKAkNSdt9r0tG_hcHj5iCy1Dp9otWGA,3977
|
||||
matplotlib/streamplot.py,sha256=_ARll6zMPC0-eLPBW-svHOs4wcG8W2tTABIcFWVwN3I,21933
|
||||
matplotlib/style/__init__.py,sha256=EExOAUAq3u_rscUwkfKtZoEgLA5npmltCrYZOP9ftjw,67
|
||||
matplotlib/style/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/style/__pycache__/core.cpython-36.pyc,,
|
||||
matplotlib/style/core.py,sha256=8LwOz9KtNuYLOzYiUyeUJNN_w-y_LsAGRhTN_SvCftA,7901
|
||||
matplotlib/table.py,sha256=fp3kcp88hOthPbc02LwZbnvbQ3__vne_LpOk2Bw_e2o,21917
|
||||
matplotlib/testing/__init__.py,sha256=z-NqrY_YBuiQGl4QVqRYAGOZcyGnSIcI16XjAbmIsjM,1498
|
||||
matplotlib/testing/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/testing/__pycache__/compare.cpython-36.pyc,,
|
||||
matplotlib/testing/__pycache__/conftest.cpython-36.pyc,,
|
||||
matplotlib/testing/__pycache__/decorators.cpython-36.pyc,,
|
||||
matplotlib/testing/__pycache__/determinism.cpython-36.pyc,,
|
||||
matplotlib/testing/__pycache__/disable_internet.cpython-36.pyc,,
|
||||
matplotlib/testing/__pycache__/exceptions.cpython-36.pyc,,
|
||||
matplotlib/testing/compare.py,sha256=vklvca9q772lqZ7wsaHxI2n3MY65MCHnKTjFKV48R6s,17552
|
||||
matplotlib/testing/conftest.py,sha256=NYJUijf2Rm25TwLysGfCNFKPEtD_zK9sWG5lcfUZS58,3253
|
||||
matplotlib/testing/decorators.py,sha256=zOMJfeD75zD8OA5MrxsRCTOLiDGlZaMERNGw5LyBrN8,17704
|
||||
matplotlib/testing/determinism.py,sha256=4GADMpjbO3127YtWNK2bNB2HTUWUkyEEQB397LGCpB0,4389
|
||||
matplotlib/testing/disable_internet.py,sha256=YE5szJX8tNHyK8j90uPUOJO76MmUHKrXFqRnj_xGyu0,4747
|
||||
matplotlib/testing/exceptions.py,sha256=72QmjiHG7DwxSvlJf8mei-hRit5AH3NKh0-osBo4YbY,138
|
||||
matplotlib/testing/jpl_units/Duration.py,sha256=FDR6rn_Yvc3XlfLuJe4KI6ALMSJCytZGZX5xIQz-yuc,6875
|
||||
matplotlib/testing/jpl_units/Epoch.py,sha256=1Htkm5XtuToVC5otGBWgsOlhPVH4FcV8EA9CkNHNwI8,7859
|
||||
matplotlib/testing/jpl_units/EpochConverter.py,sha256=NbxO32pLLg7hMuiiB2QiAuiipGAfT6q2tSSf9Xys26o,5424
|
||||
matplotlib/testing/jpl_units/StrConverter.py,sha256=g2EEkhg4ZdT8PSB-4MjPDNRbilRp7Wi72mTnsv7Ty7g,5295
|
||||
matplotlib/testing/jpl_units/UnitDbl.py,sha256=b_4G6NaHJUl4Xd-NE1BZzombIHyOGO0i6Vb0MQGBeuw,9725
|
||||
matplotlib/testing/jpl_units/UnitDblConverter.py,sha256=9AIYgnnR78G0E0D3rI20IgTawiCYC2bA_izCmy5csNo,5419
|
||||
matplotlib/testing/jpl_units/UnitDblFormatter.py,sha256=bE8adtYRXG5gzQObrzR-ROLwkFkpru7GDjjUIlRh7Ss,1416
|
||||
matplotlib/testing/jpl_units/__init__.py,sha256=fWVROJbodccLPtdnFzhV8ItE1Dl1uinQc9HcYz4hmpE,3062
|
||||
matplotlib/testing/jpl_units/__pycache__/Duration.cpython-36.pyc,,
|
||||
matplotlib/testing/jpl_units/__pycache__/Epoch.cpython-36.pyc,,
|
||||
matplotlib/testing/jpl_units/__pycache__/EpochConverter.cpython-36.pyc,,
|
||||
matplotlib/testing/jpl_units/__pycache__/StrConverter.cpython-36.pyc,,
|
||||
matplotlib/testing/jpl_units/__pycache__/UnitDbl.cpython-36.pyc,,
|
||||
matplotlib/testing/jpl_units/__pycache__/UnitDblConverter.cpython-36.pyc,,
|
||||
matplotlib/testing/jpl_units/__pycache__/UnitDblFormatter.cpython-36.pyc,,
|
||||
matplotlib/testing/jpl_units/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/tests/__init__.py,sha256=g3AQvrbsKOFFnkrCoQzlqr1cXCDV0LvGsPnvPhNOrYs,463
|
||||
matplotlib/tests/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/conftest.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_afm.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_agg.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_animation.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_arrow_patches.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_artist.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_axes.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_backend_bases.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_backend_nbagg.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_backend_pdf.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_backend_pgf.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_backend_ps.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_backend_qt4.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_backend_qt5.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_backend_svg.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_backend_tools.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_backends_interactive.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_basic.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_bbox_tight.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_category.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_cbook.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_collections.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_colorbar.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_colors.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_compare_images.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_constrainedlayout.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_container.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_contour.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_cycles.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_dates.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_dviread.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_figure.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_font_manager.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_gridspec.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_image.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_legend.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_lines.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_marker.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_mathtext.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_matplotlib.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_mlab.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_offsetbox.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_patches.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_path.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_patheffects.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_pickle.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_png.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_preprocess_data.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_pyplot.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_quiver.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_rcparams.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_sankey.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_scale.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_simplification.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_skew.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_spines.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_streamplot.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_style.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_subplots.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_table.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_texmanager.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_text.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_ticker.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_tightlayout.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_transforms.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_triangulation.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_ttconv.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_type1font.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_units.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_usetex.cpython-36.pyc,,
|
||||
matplotlib/tests/__pycache__/test_widgets.cpython-36.pyc,,
|
||||
matplotlib/tests/cmr10.pfb,sha256=_c7eh5QBjfXytY8JBfsgorQY7Y9ntz7hJEWFXfvlsb4,35752
|
||||
matplotlib/tests/conftest.py,sha256=QtpdWPUoXL_9F8WIytDc3--h0nPjbo8PToig7svIT1Y,258
|
||||
matplotlib/tests/mpltest.ttf,sha256=Jwb2O5KRVk_2CMqnhL0igeI3iGQCY3eChyS16N589zE,2264
|
||||
matplotlib/tests/test_afm.py,sha256=64Qvm_dkFOh88o8oBouswePl1kgSUE_37jrRN0__jng,2218
|
||||
matplotlib/tests/test_agg.py,sha256=aorEpO-NshLjutQQV-yF1ZikLIqss9fF7ouXsjG7o3s,7299
|
||||
matplotlib/tests/test_animation.py,sha256=rMZwOf3WEVT7xGYDYc2xkx6XjW8_mQ0kFL3wouIaZ0g,8267
|
||||
matplotlib/tests/test_arrow_patches.py,sha256=dfRDXMnKZt0FBY5JAtjKx8qbCnHCzAQoFhuEy3EzhPg,5950
|
||||
matplotlib/tests/test_artist.py,sha256=PjaIk-F4jN0QEhPm3ZinWIMOB04LesHnMe0xmLmlm4g,9203
|
||||
matplotlib/tests/test_axes.py,sha256=QQbwWolkRT1jytrYIp1tzsJN1tbpqF7tL0M4mIAa148,189994
|
||||
matplotlib/tests/test_backend_bases.py,sha256=_jib8QrQa98qAarlE01aIk7Hkfs1gQbcVDIMGzsVr2U,3585
|
||||
matplotlib/tests/test_backend_nbagg.py,sha256=TvfgH4gOzFxGRvNohjn1c9vBqEKL4OI6IWwBnZdBWgg,1066
|
||||
matplotlib/tests/test_backend_pdf.py,sha256=6D7Yah7exx8xFwGsfm5ZcjYcxnRtZs0D8l3z1UZ7qPI,7925
|
||||
matplotlib/tests/test_backend_pgf.py,sha256=GEYeAku09gn3erePaZMHRWVUBAFwuzdAXmrnFl5a4mc,8157
|
||||
matplotlib/tests/test_backend_ps.py,sha256=KX7Xak4Yb2UMD6KEiM1-UJTtA5MwbSYhRg3l5gyofWI,4505
|
||||
matplotlib/tests/test_backend_qt4.py,sha256=gNbJ4BFnwhZ-LGZ9HlXMYtQPsgPAHoYSCs8TDCFmPUA,4199
|
||||
matplotlib/tests/test_backend_qt5.py,sha256=Pf4B8pNrh8qeh-vKeVeip6_fr2UhCy1fH6HDxLC0Edg,6574
|
||||
matplotlib/tests/test_backend_svg.py,sha256=fbp1hAcPoxetMsfRdZ50ZttMX-vY1eqSiBfFtCXKB80,5439
|
||||
matplotlib/tests/test_backend_tools.py,sha256=C-B7NCkyWsQ5KzQEnI5Be16DsAHHZJU9P5v9--wsF-o,501
|
||||
matplotlib/tests/test_backends_interactive.py,sha256=AuioLNYNgZZ4Q5HJWh58CLsX5CRW0ZJBQrPYZn6DE68,5012
|
||||
matplotlib/tests/test_basic.py,sha256=jexF5eJw4gwb0hYMx6_u8wJa3hgtwKclag1Lhws_f7U,730
|
||||
matplotlib/tests/test_bbox_tight.py,sha256=ZcHz4qXTRhiVOiGQgSaqQgA7IjsRh84qDYOoVe82IH4,3280
|
||||
matplotlib/tests/test_category.py,sha256=HJ1oBEZD0A6wHUnrz49kymv93UX0fkkdFCclIBrI09w,10303
|
||||
matplotlib/tests/test_cbook.py,sha256=Tm8U0CgUpWthx7xQ_DicpslnOHlXnG9Wr0bO9E-YJAM,15647
|
||||
matplotlib/tests/test_collections.py,sha256=no4WHkfoo6s6Mlc0hrrd_ssl9bpapGGTMJ7WsvCh8Hg,22100
|
||||
matplotlib/tests/test_colorbar.py,sha256=YbP1IZp9HftMxpUa_yvVKFJexKCGabXQkZSaPam8qvM,17071
|
||||
matplotlib/tests/test_colors.py,sha256=JrmZ9kvwN21bUwwa2YMAhrlURLRYDGDAA0yMlOHN5nc,25629
|
||||
matplotlib/tests/test_compare_images.py,sha256=n3Uoukid0GcjyQpd6ZrqIY9u3RLNE2XAPWwtcHIsqto,3155
|
||||
matplotlib/tests/test_constrainedlayout.py,sha256=easxoL_jBZe5ChE7eQcjz6aqx4LPxQde9FkRsWoOg5I,13261
|
||||
matplotlib/tests/test_container.py,sha256=ijZ2QDT_GLdMwKW393K4WU6R7A5jJNaH1uhLRDwoXXI,550
|
||||
matplotlib/tests/test_contour.py,sha256=2Yl9crBjJegsAEco-mo6oa31TfoJkupLbqlwl9IFkWU,12909
|
||||
matplotlib/tests/test_cycles.py,sha256=75QI7uIkh4b5ckGd0B7irJuqhIivYx8CmxRAciWRj1g,7490
|
||||
matplotlib/tests/test_dates.py,sha256=SDos5lnmY_dNNIT-hjVZ3e5OpmSePAOIWFJoA_9tDdM,25085
|
||||
matplotlib/tests/test_dviread.py,sha256=kTk9Qv6q9Kk3fJcDAEWm35HF-sKsP6Ybec6N8jEHasE,2342
|
||||
matplotlib/tests/test_figure.py,sha256=LVk5dyf6H8V8Ot2bIQXAnscaqLegeDfud1wtLXusJXo,14204
|
||||
matplotlib/tests/test_font_manager.py,sha256=8dVBlum-bY9gmy_YVXWERbWtbGNQllwzCyCNxffGCFI,3271
|
||||
matplotlib/tests/test_gridspec.py,sha256=zahj5Rd4pB0xtAc_3KX7fQWyBys0P-IQk-Cq0cs8VgY,626
|
||||
matplotlib/tests/test_image.py,sha256=uAMkqy06wYZeJASFKIt-hdWCG310dL-AFp6cnLdnjlM,28263
|
||||
matplotlib/tests/test_legend.py,sha256=2Q6Qn2C72wRqZvY6Mt9v_udD0bVARDTbWcqBZD1-oCk,19782
|
||||
matplotlib/tests/test_lines.py,sha256=4n7pdODOXqLvX1zing9SuXXxChPDe7nEr42gaiIG8d8,6221
|
||||
matplotlib/tests/test_marker.py,sha256=fhHHW93wCl5KbrZRL2iEVziv2BLBZU6zSt66hgsI5jY,739
|
||||
matplotlib/tests/test_mathtext.py,sha256=3G0W1S2J5QOHs0EfQiAdP5M114sh81sJtDxITrF1gzs,12696
|
||||
matplotlib/tests/test_matplotlib.py,sha256=DIBqISzUIYanSxNWJL9n2oob1dRLOOAr6TIz2BTWK1I,706
|
||||
matplotlib/tests/test_mlab.py,sha256=gpd4pJ8fIQNCXdwJV1C1__EKBMdnkvQLVgyFdgNgrZc,96981
|
||||
matplotlib/tests/test_offsetbox.py,sha256=u_VCL-lWvQ66IGTzuotzRxq23ySzcB86Gjvmeib-S3w,4198
|
||||
matplotlib/tests/test_patches.py,sha256=IbeCN3mVunvkukneSZKjVbZKGJg0h2oqmOzrI400HQ0,16218
|
||||
matplotlib/tests/test_path.py,sha256=EwvnGhKmdMFl5w4PC2KWwKVm2qKLuRksM70kb-MUVTU,7164
|
||||
matplotlib/tests/test_patheffects.py,sha256=Sl90AY4wri37bEvRuS3QEthE_GV5i5drh0Yk2oGJjc0,5372
|
||||
matplotlib/tests/test_pickle.py,sha256=38XMrLSsH7RgNVesFLB68UAD6HZS7Eylbs3DktWmkBw,5533
|
||||
matplotlib/tests/test_png.py,sha256=3TieTdSRvSL9mS7bhxpozw1CClQxKYunK83TLPU4bnA,1786
|
||||
matplotlib/tests/test_preprocess_data.py,sha256=oJuYg8kpqgLrA1wYEUzMLULRoKM3IRf9_ejRzPzM0AU,15990
|
||||
matplotlib/tests/test_pyplot.py,sha256=wOld704X_pAZVNvnL3WD2KUHliRPtAoqLBh0-OrdeGg,974
|
||||
matplotlib/tests/test_quiver.py,sha256=hAKbP4S6npdpIfwjw6efhMr6En2A-0fJAjLOiMofxWk,5789
|
||||
matplotlib/tests/test_rcparams.py,sha256=bmDAFAz3g0ZLHLVqVU9F4POt-X5HbAqpLNfBp4m81N8,20498
|
||||
matplotlib/tests/test_rcparams.rc,sha256=zwPbYzajd7FTIYURvpwTBAn8i060Do3OVDGZ2xHZeLw,74
|
||||
matplotlib/tests/test_sankey.py,sha256=ZZBtNqIsFcJLoZTCKSM2xaUfrtMjoSica3Mi-FtMysw,162
|
||||
matplotlib/tests/test_scale.py,sha256=8V-tt5E79R-P_Zz6e7iH_7KCHU6e1Ql2SLnQdpt7BmQ,4100
|
||||
matplotlib/tests/test_simplification.py,sha256=c1GSkJwiGxksnUj5WgYbvsr3yDcmbEKHmB_12W_jp7s,10895
|
||||
matplotlib/tests/test_skew.py,sha256=zOhGb5V-9A531ZpmHlqFsdTL9xfdj2-RL3N7OQJbV20,7058
|
||||
matplotlib/tests/test_spines.py,sha256=1cN5KequShVG83DggeUxt5QWE9uIkfmyONaoVNJOojw,2326
|
||||
matplotlib/tests/test_streamplot.py,sha256=F-ilyMfXm47d-XCz6L06GE8KQ-bEHbhKLqIS0EVKv0I,3497
|
||||
matplotlib/tests/test_style.py,sha256=JAcu_9wDS_Gvtisce9Z6rHrO-SHZVjyPeBDYfKgDZ1c,5333
|
||||
matplotlib/tests/test_subplots.py,sha256=yMzfFiUpZBcBt2FpLFySHzxmoj2B4jevjLviFWkPouo,5551
|
||||
matplotlib/tests/test_table.py,sha256=Qoe4Sm-yyog-NOHASdBOjjGQrFQOo2amm01KAgP5MJo,5906
|
||||
matplotlib/tests/test_texmanager.py,sha256=zCtJ3JnZNfP2AQNy7q2LQAgaflSe7S5htJkJNylQSGE,459
|
||||
matplotlib/tests/test_text.py,sha256=Vg5HlZU9ob584X5AZAxQuIm6PFj_GO9xnxIBKZU7O2c,15435
|
||||
matplotlib/tests/test_ticker.py,sha256=OPuJl75xgr_AZ8IDUuyi90PgAXBVT91EnORB2h31mUQ,32802
|
||||
matplotlib/tests/test_tightlayout.py,sha256=7w_LPOY1EM0Z6i7GPt1--w4z0wmf-5M6rvpd0wauqKg,10016
|
||||
matplotlib/tests/test_transforms.py,sha256=Y3FWBzEiBBydD5X4id-bgpzRSFCBxyZFtPTbto2KUv0,24713
|
||||
matplotlib/tests/test_triangulation.py,sha256=XXCtxqhP6jHFe2mbIbB0-KNsdkzxhUDI1V9bDz1A_Eg,44838
|
||||
matplotlib/tests/test_ttconv.py,sha256=xilgvzZpTpHSnumaUlHvQ_zdIQ7U7xBD1Aflx34I-xU,641
|
||||
matplotlib/tests/test_type1font.py,sha256=C0pCPBGOv49SR2xxDOq6LSXAEH_ZNvIWvr_jG-23Gmc,2097
|
||||
matplotlib/tests/test_units.py,sha256=yUnl2ds8QGdJdZWME2YHysQbSr9DG4aEkgIIstMZyMc,5196
|
||||
matplotlib/tests/test_usetex.py,sha256=9ANPkY6aKfyY0_DopPLivplxlT__v5y5wHX2_8-Z4Z8,918
|
||||
matplotlib/tests/test_utf32_be_rcparams.rc,sha256=K66jcKehDKcG1yTXJCOSsmp1iteU9Kvsd_eobV5wNW4,56
|
||||
matplotlib/tests/test_widgets.py,sha256=7sBak0W8XT-NhZ9zNEZC8i_PA6LJIkBgiE_gB42h2cE,16787
|
||||
matplotlib/texmanager.py,sha256=KBU8HQhgj0DNOTwpCZhH_pJ1IqyfEnF1IgUOBDUpRqQ,17284
|
||||
matplotlib/text.py,sha256=-q9B-fS_GmCoAEHhEDbY7m9fidyFLjSn3600KAcROkE,81684
|
||||
matplotlib/textpath.py,sha256=FLg0qVzTVf9_CFbE3O39Pw7-4hiAYiWoqz_dwKMmHYY,17917
|
||||
matplotlib/ticker.py,sha256=Pb6Eyaiz6ncDoDjE7Vew8h63i1s-x9hNR-aRHP9-fhs,88149
|
||||
matplotlib/tight_bbox.py,sha256=Yf5X-HVlMe3AqwR5tUJQBH0LLZqOGoh2uP0oJnU6ErA,2463
|
||||
matplotlib/tight_layout.py,sha256=iQt1J_nr40VPf3GCgqed_r_umsYE5lnvjWxLdh3-uPs,14562
|
||||
matplotlib/transforms.py,sha256=2pDKJqB7Fnx2WLvWUKY8wG7ry9xk0xtVq9dDu32HvLc,99669
|
||||
matplotlib/tri/__init__.py,sha256=XMaejh88uov7Neu7MuYMyaNQqaxg49nXaiJfvjifrRM,256
|
||||
matplotlib/tri/__pycache__/__init__.cpython-36.pyc,,
|
||||
matplotlib/tri/__pycache__/triangulation.cpython-36.pyc,,
|
||||
matplotlib/tri/__pycache__/tricontour.cpython-36.pyc,,
|
||||
matplotlib/tri/__pycache__/trifinder.cpython-36.pyc,,
|
||||
matplotlib/tri/__pycache__/triinterpolate.cpython-36.pyc,,
|
||||
matplotlib/tri/__pycache__/tripcolor.cpython-36.pyc,,
|
||||
matplotlib/tri/__pycache__/triplot.cpython-36.pyc,,
|
||||
matplotlib/tri/__pycache__/trirefine.cpython-36.pyc,,
|
||||
matplotlib/tri/__pycache__/tritools.cpython-36.pyc,,
|
||||
matplotlib/tri/triangulation.py,sha256=YM3AIH44SK4LOJiLPB4Wo-DQ1d2q75SdUQ_W2kaxrO4,8427
|
||||
matplotlib/tri/tricontour.py,sha256=Uz3bHK7xw3fHnjFstOlurnz9-btm6dfa0lKE7Vt_P0k,9375
|
||||
matplotlib/tri/trifinder.py,sha256=_S-whwBCe5m9byOzcdAXFJXs0gAIXqy9rVGkXKiM14U,3505
|
||||
matplotlib/tri/triinterpolate.py,sha256=uWh1PPiaN0nMM30txiEVoAxtFZXvX0hxvPnWzKP3xoc,64969
|
||||
matplotlib/tri/tripcolor.py,sha256=DwBFSsZ_jBrFKIPrYetMXNqy_i9GS9-BQUDjPig2WOw,5326
|
||||
matplotlib/tri/triplot.py,sha256=aZ9O_VVLH0AOne31u11ltLlyVyhqKtyzec7WH3b3pkk,2857
|
||||
matplotlib/tri/trirefine.py,sha256=DZS_gihMxkUMzuxAijKnEDo4Po_ahIHY7-uGnjUY1Eg,14142
|
||||
matplotlib/tri/tritools.py,sha256=dC_OcwrFN3gunCe3SgHjQTH_dHBCncM1Fex0bQ_b1Jg,12498
|
||||
matplotlib/ttconv.cpython-36m-x86_64-linux-gnu.so,sha256=tnAP4SNjDwMwdCZFJjzoDYLYa0wf3LAMZmKd9M_QmS4,83888
|
||||
matplotlib/type1font.py,sha256=aBak-e5VKpZpH_LyYqNUyAX4vgRisZq4sfeEcV45-j4,12173
|
||||
matplotlib/units.py,sha256=URBYLJTwt1n-w7A0LcznVlMCMMnLaJFEVrhEHTt0Yv8,6665
|
||||
matplotlib/widgets.py,sha256=vAYlyIJUze6G7CW9CUmY1LePR5L5XFJrPqvVIYycWzY,94072
|
||||
mpl_toolkits/axes_grid/__init__.py,sha256=d0j8ET68OmR22G59uzWO4BQ3Jv2kCmJC15nZRAf227M,559
|
||||
mpl_toolkits/axes_grid/__pycache__/__init__.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/anchored_artists.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/angle_helper.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/axes_divider.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/axes_grid.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/axes_rgb.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/axes_size.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/axis_artist.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/axisline_style.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/axislines.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/clip_path.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/colorbar.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/floating_axes.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/grid_finder.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/grid_helper_curvelinear.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/inset_locator.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/__pycache__/parasite_axes.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid/anchored_artists.py,sha256=_F6-9iacZidb5JpJ8jCOZ9PdiZaR5qpfBjf-3VjTzNc,291
|
||||
mpl_toolkits/axes_grid/angle_helper.py,sha256=Tb4Mb_NGkUdkisebe2dqfBdFmUZiSmGyUnftiSeSIls,51
|
||||
mpl_toolkits/axes_grid/axes_divider.py,sha256=Q1NvDXXtKVuX7iUoKFFRw11Wg1eHEGt3-qDWG7DOVxg,269
|
||||
mpl_toolkits/axes_grid/axes_grid.py,sha256=t2Fc8fM-_qINumuDxctOEYhMI3M1ZfqEVc3th-cnz5g,152
|
||||
mpl_toolkits/axes_grid/axes_rgb.py,sha256=nKv0IWpKHN2NW5eZqx_rbaZqqMWvYw9GK94gIAVEmE0,301
|
||||
mpl_toolkits/axes_grid/axes_size.py,sha256=v4Nhxe7DVp1FkKX03DqJJ1aevDanDvgKT9r0ouDzTxw,48
|
||||
mpl_toolkits/axes_grid/axis_artist.py,sha256=zUlJFUHueDsMtzLi_mK2_Wf-nSBQgiTsMOFpo_SngZ0,50
|
||||
mpl_toolkits/axes_grid/axisline_style.py,sha256=lNVHXkFWhSWPXOOfF-wlVkDPzmzuStJyJzF-NS5Wf_g,53
|
||||
mpl_toolkits/axes_grid/axislines.py,sha256=kVyhb6laiImmuNE53QTQh3kgxz0sO1mcSMpnqIdjylA,48
|
||||
mpl_toolkits/axes_grid/clip_path.py,sha256=s-d36hUiy9I9BSr9wpxjgoAACCQrczHjw072JvArNvE,48
|
||||
mpl_toolkits/axes_grid/colorbar.py,sha256=DckRf6tadLeTNjx-Zk1u3agnSGZgizDjd0Dxw1-GRdw,171
|
||||
mpl_toolkits/axes_grid/floating_axes.py,sha256=i35OfV1ZMF-DkLo4bKmzFZP6LgCwXfdDKxYlGqjyKOM,52
|
||||
mpl_toolkits/axes_grid/grid_finder.py,sha256=Y221c-Jh_AFd3Oolzvr0B1Zrz9MoXPatUABQdLsFdpw,50
|
||||
mpl_toolkits/axes_grid/grid_helper_curvelinear.py,sha256=nRl_B-755X7UpVqqdwkqc_IwiTmM48z3eOMHuvJT5HI,62
|
||||
mpl_toolkits/axes_grid/inset_locator.py,sha256=qqXlT8JWokP0kV-8NHknZDINtK-jbXfkutH_1tcRe_o,216
|
||||
mpl_toolkits/axes_grid/parasite_axes.py,sha256=kCFtaRTd0O8ePL78GOYvhEKqn8rE9bk61v0kVgMb6UE,469
|
||||
mpl_toolkits/axes_grid1/__init__.py,sha256=SEWPa2ggZnKkFVX4yaJOPN7KgyV_T-cyjr8UjIjjhPs,272
|
||||
mpl_toolkits/axes_grid1/__pycache__/__init__.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid1/__pycache__/anchored_artists.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid1/__pycache__/axes_divider.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid1/__pycache__/axes_grid.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid1/__pycache__/axes_rgb.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid1/__pycache__/axes_size.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid1/__pycache__/colorbar.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid1/__pycache__/inset_locator.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid1/__pycache__/mpl_axes.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid1/__pycache__/parasite_axes.cpython-36.pyc,,
|
||||
mpl_toolkits/axes_grid1/anchored_artists.py,sha256=SPXVgw8CLMGTyPScN3Q2WHeWJbhtQo52Fo3DaLJ8yrY,21162
|
||||
mpl_toolkits/axes_grid1/axes_divider.py,sha256=RNaghesva1N6F4h6J-5Amy15LVqzQyW-FelkfQ_m9n8,29873
|
||||
mpl_toolkits/axes_grid1/axes_grid.py,sha256=BOh_MSGrTK9_-rAFuY5UST84PRDLObCj2tOg71wYjXI,27243
|
||||
mpl_toolkits/axes_grid1/axes_rgb.py,sha256=UlErJuhcqtN0skRLckf_v-GvUtAWDTVW401wUzwXOxI,6603
|
||||
mpl_toolkits/axes_grid1/axes_size.py,sha256=m4LSknVO9c6vcpT1bEZBKYUGkoJ7BOrPnLRTcLmnmFQ,8933
|
||||
mpl_toolkits/axes_grid1/colorbar.py,sha256=BLNBORudFV18ShwQiiVdceUOTJESQZ4yefVu23yVex0,27428
|
||||
mpl_toolkits/axes_grid1/inset_locator.py,sha256=EeLwbA6sUhCBFzPVA1MwCTGE8N1APdKZXq4xSiCXG1Y,23722
|
||||
mpl_toolkits/axes_grid1/mpl_axes.py,sha256=nUXeFjye-NBR1SCXYx1qirV6QIYSB2e01AEeoWlzm7w,4680
|
||||
mpl_toolkits/axes_grid1/parasite_axes.py,sha256=TjNMInmARhT4tZn2PBafcecuyammO3KBuCnUUYyhbdc,12908
|
||||
mpl_toolkits/axisartist/__init__.py,sha256=2zsgjqTtP_NXv78MEaKabmfmkjA7yhy77pIcaR57YWs,748
|
||||
mpl_toolkits/axisartist/__pycache__/__init__.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/angle_helper.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/axes_divider.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/axes_grid.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/axes_rgb.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/axis_artist.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/axisline_style.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/axislines.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/clip_path.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/floating_axes.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/grid_finder.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/grid_helper_curvelinear.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/__pycache__/parasite_axes.cpython-36.pyc,,
|
||||
mpl_toolkits/axisartist/angle_helper.py,sha256=csNOzIHc3s1fvJm7XpuZy74kQY2oeC5przHcVSSFVLc,12932
|
||||
mpl_toolkits/axisartist/axes_divider.py,sha256=FqYC72nAYkmU9oaawDb7TjMxb1NSjhbYocD1vxwCrvM,509
|
||||
mpl_toolkits/axisartist/axes_grid.py,sha256=vfd_EXHuYQ7iIVK2FOm6inLhb7huZxtOSvFyOVW2GmU,610
|
||||
mpl_toolkits/axisartist/axes_rgb.py,sha256=TpJCB8eA0wHZVXOxxfFoy1Tk_KFj68sZvo74doDeHYE,179
|
||||
mpl_toolkits/axisartist/axis_artist.py,sha256=4IdmKz2zRHnRCerIO-lYnf7LiUphSE7fe_Hq5VapDms,44753
|
||||
mpl_toolkits/axisartist/axisline_style.py,sha256=It8dzmdESmoAmwwEjOs-YjtBydKlNmb41vV6v8tZ1-s,5107
|
||||
mpl_toolkits/axisartist/axislines.py,sha256=63q7XMODxvM3mwHCmBvtczaOgre-dGigNqavgrDZ844,22082
|
||||
mpl_toolkits/axisartist/clip_path.py,sha256=_fxHB05pFazHxDmNRXN7xO5EfJaxFPHMwFfqwfAs2uA,3736
|
||||
mpl_toolkits/axisartist/floating_axes.py,sha256=B3_1_qTFDSWIpfbRxMf9TXyoRzQmwMISdpqU46nC-Uc,16491
|
||||
mpl_toolkits/axisartist/grid_finder.py,sha256=E_JrpMeAIUj9FZ6Q7_rd4cEYeDbb5TgjoOsJ_5YQIoc,11513
|
||||
mpl_toolkits/axisartist/grid_helper_curvelinear.py,sha256=yH4--wkTm2C2FSSc_TQcvU-24wJlJLj18JphW5Kzz80,15491
|
||||
mpl_toolkits/axisartist/parasite_axes.py,sha256=1sQwBEYuXHpaEeObb7cXh0I1xWroYtcvFiEmwrzqK3w,447
|
||||
mpl_toolkits/mplot3d/__init__.py,sha256=V2iPIP9VyRhoJsFWnQf5AkfyI1GSSP9H6hICEe9edJo,27
|
||||
mpl_toolkits/mplot3d/__pycache__/__init__.cpython-36.pyc,,
|
||||
mpl_toolkits/mplot3d/__pycache__/art3d.cpython-36.pyc,,
|
||||
mpl_toolkits/mplot3d/__pycache__/axes3d.cpython-36.pyc,,
|
||||
mpl_toolkits/mplot3d/__pycache__/axis3d.cpython-36.pyc,,
|
||||
mpl_toolkits/mplot3d/__pycache__/proj3d.cpython-36.pyc,,
|
||||
mpl_toolkits/mplot3d/art3d.py,sha256=Juj1shJAObe_JDhF3Fm4qtMVPmvLMUYt2lKp8dakteQ,24879
|
||||
mpl_toolkits/mplot3d/axes3d.py,sha256=EDZc-nuqbI6HNt7oioFRL36EKODH8HTA68EsElSW_II,103257
|
||||
mpl_toolkits/mplot3d/axis3d.py,sha256=z8Q20DsMzFvA-jYWFsbbo-fFB7Yzegdlg7Wz1Ws5VPE,18574
|
||||
mpl_toolkits/mplot3d/proj3d.py,sha256=JNb8VcfoAOwRJMLAOT6pdX2PDE7fCx5L48PGdIACzvU,4612
|
||||
mpl_toolkits/tests/__init__.py,sha256=iPdasxJf0vpIi11tQ98OVSQgS0UaPUyOEGGfAryAhIA,381
|
||||
mpl_toolkits/tests/__pycache__/__init__.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/conftest.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/test_axes_grid.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/test_axes_grid1.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/test_axisartist_angle_helper.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/test_axisartist_axis_artist.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/test_axisartist_axislines.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/test_axisartist_clip_path.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/test_axisartist_floating_axes.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/test_axisartist_grid_finder.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/test_axisartist_grid_helper_curvelinear.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/__pycache__/test_mplot3d.cpython-36.pyc,,
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid/imagegrid_cbar_mode.png,sha256=yvo6erXXc3Z9aO0rrEezBooCc6KhAw7wKv4WngOQmFA,87393
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/anchored_direction_arrows.png,sha256=XMZGgG7_9k96bKhI2G--XBVKpct5O5psbGH2Wvj5YA0,10784
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/anchored_direction_arrows_many_args.png,sha256=fkPsdmhd4S1g-QxMb55M63iAgWmC2G4ytcLOT9tMAD0,11039
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/divider_append_axes.pdf,sha256=eW2CuM_T4d95dC-DU0PmmQD7gqRQIO0rcQpvp-zu1i4,25446
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/divider_append_axes.png,sha256=VfRfs6p4akgjGxxNm6Bu83Pg0v1KmU7WPu97_-kzNFc,48825
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/divider_append_axes.svg,sha256=usfsa3y-s-N2KMOzsOZHTq-PZXgAPXsSM-lkxJ3ZUi0,172812
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/fill_facecolor.png,sha256=Tkrylxebxm8SuWZjQK0qXSX8m9QsQU6kYm7L2dgt4yg,14845
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/image_grid.png,sha256=EStruex2GqxBIGm49SXqrn8lO4-_XhsAnvs5CmtUawc,3872
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/inset_axes.png,sha256=RQmR39E6Vskvl7G4LInHibW9E1VK0QgCvI-hBlb-E2E,9928
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/inset_locator.png,sha256=bQKKKUuoU_EZwZT_9FzzeVKsKwUUBOZV55g4vVUbnCU,9490
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/inverted_zoomed_axes.png,sha256=rvglsLg8Kl9jE_JukTJ5B3EHozsIYJsaYA0JIOicZL8,25997
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/twin_axes_empty_and_removed.png,sha256=0YzkFhxs4SBG_FEmnWB10bXIxl9aq7WJveQAqHm0JrQ,37701
|
||||
mpl_toolkits/tests/baseline_images/test_axes_grid1/zoomed_axes.png,sha256=mUu8zJtz8FMb7h5l4Cfp3oBi9jaNR5OoyaDgwqpAZp4,25893
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist.png,sha256=qdlk9UPScCAN9RBOhoNqLmJvmkXt8pCuwuQtrz5E8Bs,10151
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_labelbase.png,sha256=An5lovtvAiNA1NZI-E8kOj6eYTruQMqwf3J7pXwdk4A,10598
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_ticklabels.png,sha256=7vuAKkIqcpgJrc2AF7oslf-E_sDfSlCoymyc87u4AWs,5696
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_ticks.png,sha256=CkVtCWG13ViW0w2DsbzfXSvoFWHYaaqQYeEYpbKbOg8,5695
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_axislines/ParasiteAxesAuxTrans_meshplot.png,sha256=FOgl-Glmzhdp6V8mz4StofTsFXGysFkEcUeaWtmJDZs,34354
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_axislines/Subplot.png,sha256=tRpYCjR5zUkafA85DVmY3duTEouwCZq6jDwSF4UsBS8,26919
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_axislines/SubplotZero.png,sha256=3kCrz7HQMYrK3iDgYgf8kyigxRtIGFBbcUzJPtiXh_E,28682
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_clip_path/clip_path.png,sha256=BtMyb7ZawcgId9jl1_qW72lU_ZyxLN780uQ9bCLjbHA,25701
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_floating_axes/curvelinear3.png,sha256=4th7Y74_9YV6X25RqJW0Op9WDzGRCcxF1kfNogkgozE,52835
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_floating_axes/curvelinear4.png,sha256=cYjrSiH6Mvor-VhmwNUgX7Le3_k1rurpd8L5vhTf16s,29374
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/axis_direction.png,sha256=3fue92dg-ntYI0XX0nB31IFpgRT2V3izqjrmLvEdYN4,40536
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/custom_transform.png,sha256=4cQhIFK1z8oPUVyvkHNZ_m-GCbikmUbTvkvYVGy6U4o,15118
|
||||
mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/polar_box.png,sha256=wWaPM3I7_435SkVQqIggb8BHrWBMWrsSVyMZQQJ6fE4,62526
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_cla.png,sha256=htnP1CA8dd85KqdnOsHVlsehT90MUoQD8kFTyra0AuE,51409
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_labelpad.png,sha256=zrLsk8t7s970yaY3cqj6SOMbI6UY8Loe0Zbp0WqFtwQ,66817
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.pdf,sha256=zkfSOR2bJYC_X425qnXHMmGJPSlLSpFs53YB_R760Gw,6603
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.png,sha256=SoyN30SsuvEECZyB_ReGP3ZKGZJazOp05dXa3YUn7Jc,47796
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.svg,sha256=Kb_zdIzZG6JKnztMcmOUG4esPsuteljB_A2sxrhIA3Y,18046
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.pdf,sha256=YI5gzs8NK6fWo6JB0wf8xeZ-FrHlS3P8DSCccsLU4fE,7197
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.png,sha256=Qw909B4nDmV9DlMuo1DKk7y5ndjtvni5d_DcysmG9VA,100466
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.svg,sha256=iU1Pk60SDC89km6bwz9Li9mXdNdZ_Vn15bkbAUG2iKo,30591
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_notshaded.png,sha256=p9nV0cr7ZqhmM5VRHYcByR_QWKh91b8jjt71nkrq3Bc,66289
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_shaded.png,sha256=9RXKAlcPSrQYmhvqH9JMnlLaR62satjwyUq1joXcy6g,64595
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.pdf,sha256=CtzH5MJNMY_hZGAyp9py9PLI0a8kJ-jNnpQKQYtoQEE,25170
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.png,sha256=tii1IakS8MC_Vuwd95HhcyM0iq4zGN5DxgRFfB9mKu8,83161
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.svg,sha256=3VjmHaN5hRXJ-HH9duS1M6nR8gkgBOCtb3zruWcSujU,67618
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.pdf,sha256=H7FyZjuiA12CzP8FDDnVNf42HxGfMlg_8rA57HA1sIo,52812
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.png,sha256=Jb-fhAcgogE8jn9DSsaqInUfWC7D_5Pf3QRf7XWAX2Q,42575
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.svg,sha256=cz9TicdIGJ_8UdHTKQZ76n2rAv6Rx2EELPg8AyAEgMU,173077
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.pdf,sha256=raTRNOdfYPFJvZiM7BbRbdWRPElQe0sn6jsKH63TTyA,3950
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.png,sha256=dE8eHoj43eePB44F1nLM2RLj8iqw8rCYI3D0VD3gUg0,39694
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.svg,sha256=BM-PcmZJ-8iyB4wUWQxcMuskmDXemrsX52LKPYEz850,11093
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.pdf,sha256=6pKrI8lUIPxRwi3ofm8DK8UqNeZsBYprXwX51vwFv1Y,4354
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.png,sha256=DQT-NruHCeG5LKpjG-dlLln3aCoPKhua5PQnHTafBGU,60217
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.svg,sha256=c32m_X6tFYlY5PCVYJ0cPzvZDTbgRA-GIwzDUVTNKYA,12919
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.pdf,sha256=Y-C6q8kG4QdVsBpck1Qg90W87rSnbOzrDQP1v7qFZDI,53962
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.png,sha256=rGHpqTyXqt1PvCSvSrP7Dnd0uNeeEmPPgJwADxXdfM8,39763
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.svg,sha256=TQ9KQlKC0BU4lnayB2S8ArbsfAh87qKjFEh5WKjWbvk,286241
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/plot_3d_from_2d.png,sha256=AWos5EJWMerD0tgVZyvBofz-5hnCq6fhGHKmQi-izAg,56593
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.pdf,sha256=mJYYIj01eM9ouCPpoeWu-KDrXe3_o2um4_JddGAzWGg,2680
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.png,sha256=ePzSA-iFaQbmH603vw1jhs9vyIt45xXnbpIuUF3a1l8,52065
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.svg,sha256=K-D9vp-jB8KFTilVotfIQvuhG3qTMT04XZ6LUIfpZ60,8594
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube.png,sha256=AJ0EoayvdBoywpOUWcxbMQ0oB7cTzcoWGgGyx2qgQMU,23182
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube_ortho.png,sha256=5Phz7BclSciZpg4SDu-eUQ-v_ikHbEqReQWCdeHywQk,16210
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_lines_dists.png,sha256=XCd4hX2ckc5GCxcgenkRJ8MT7pX-3iMLylD2rCjNl-4,18898
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.pdf,sha256=T4nKDMCEi2zKpoq5FOsdyMQJd0igS9yBeyUL84a0oso,18589
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.png,sha256=PBllNI1kHf1rz-oPK507OwsPNE9DPwivXAVJM9DemBI,104755
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.svg,sha256=_cwXN4aH-mQx5ADarZtXsY-vaeRFg4dPuVmP6S9NZbM,128623
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_empty.pdf,sha256=DSFSucOBU22R3zmG8eKlWWtLy5Wb7L7wqZ1B7CMybKc,7405
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_empty.png,sha256=98D3k5QIL7KugUwzqJhdLtp9dgDGgx8hGa9_u8cvX6o,37954
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_empty.svg,sha256=H_b_HtjccyLTNDjIqUek6DwQugfsyb88nVBepBzFdTo,7745
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.pdf,sha256=_VIBNh32vX_K9QrH-0o4z13FAE0JZQuOzOfkCTsSe7c,10939
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.png,sha256=67yp7-6f-vDiYTmCqMFfuIEGly5UHCCUOV84YJtLsX8,80392
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.svg,sha256=JO8n2RUeQANeCpq_PmtyjmVPqMmuqvZQg7vk_3hsv8I,68796
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_middle.png,sha256=N4o26wMzfnyxndPbZ2VnsjIAiNYrFN9Aa40ficwO9AM,104735
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_tail.png,sha256=Ff_UrWxD-VIMQLN1uXy5u_Yd5e1P427YfGM05nvU2kE,104951
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.pdf,sha256=93umAGrsz8bYekxMETAlU67eCTY3tzycxpVmm2M7eEE,5847
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.png,sha256=MDaocusHz6Itinjm2j6fnDh-rl1fqVjnqM89nP8bwZs,43155
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.svg,sha256=yc-PIx36-DdoCE1Vd8JRih45GoBR7eVtdi6wMZVZUXU,12494
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d_color.png,sha256=Y7De9BIFLp0Ova4fk9IcXloNjiwmifTrFA1IfVJA3aE,41598
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.pdf,sha256=OSvMZiGJzmVdtv_e0XHIBw4bLSY7DLtWGDAYgh6vNuo,48096
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.png,sha256=Ok0UmO2DELze2yK8mRx0CifmRAgvjyS1IvERsBRvFlU,54712
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.svg,sha256=CaqTIT-jdKSKfCqp8GC_QAc_3Dxwgr97uvDR-a4xdcw,270325
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d_shaded.png,sha256=tW1q8o6BAlqGD-ILqAXXTBxEt08fwIwEnK-L7n4fHFo,43405
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.pdf,sha256=CfWlocbvwHK48CADjhNjjXJ2eHsSvoNWWpz33RvkuQQ,13906
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.png,sha256=cuahU107LG85pT3kyHngOPV2GchTUwu1AkLYbb4UDd8,77741
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.svg,sha256=MmcVy3u0Syymf933rIsaUY8XOv6b9r6ScVXx33NFxBg,36357
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/tricontour.png,sha256=8IjYmJP6cBhnPGLz-WDyn7UUMYZ10Kz2MpjOFwDUVow,71328
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.pdf,sha256=OVbK3ausYMApR7_UJPx0pf3bt4A3CpZumhAVhzQMbs0,169155
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.png,sha256=nO0gJBIluLEX3mlxXY3C6bx-9Jf_xJyXAnTXKnqrIkQ,99103
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.svg,sha256=PFUTTF34KiJcol6J5JmRjkaillzRWAgsbfxgEhpWxmA,103333
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d_shaded.png,sha256=45y5oF9MzbR07cP-T9CNF7yLWo8qUDoG3E91kc6jueE,94492
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png,sha256=4BUCO65oRVuxgOs3i06OFVNWi_UJLelUQzJ7WCzhdOQ,179128
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png,sha256=DMDM2KV84Z0TH7BlWJAuESle0NbFcCsmDF9Q0rPmP3w,65236
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png,sha256=ze6vMiuPmRj7FOX_1ltXMWwQNaRD5jl3drzXbdMKcFs,96319
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png,sha256=6X8fQ-dYqDlRt9oSoeYbic2mQb1DDlqPfAUpMbh2O9U,136235
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png,sha256=mG67qqJF-9TKYbacsbVknSnUOj8D9m0sYmy0imfwY2M,60940
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png,sha256=OeInzIzGNXG490czp9McRMqEMcgiz0ABkUky7QlTwfE,122177
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.pdf,sha256=3gYea_CtpNg1UmurOhyWt3vHZSkLX1-trEY779yrxbc,36169
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.png,sha256=epmsR4rWGzh7prW3RL_t8ZcUEsphM5bc0t5j__iauOY,108371
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.svg,sha256=-hjWYF-WSE3aDuCSpHYztjeV_yktEqatDK2yZyn_rhE,85892
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerocstride.png,sha256=WaO_3NcaZPFzlui5SQYJ-TbUylHkSbieneCYPffNgAA,81117
|
||||
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerorstride.png,sha256=y1JvfuVOBiNhJoJ2HdOXyBYBBkQm-oaPcoekfT-cMso,84284
|
||||
mpl_toolkits/tests/conftest.py,sha256=Ph6QZKdfAnkPwU52StddC-uwtCHfANKX1dDXgtX122g,213
|
||||
mpl_toolkits/tests/test_axes_grid.py,sha256=UCQFk5p-9sbTMCS6RKk7BfyCiTb9yRnsarH23eUem0o,1638
|
||||
mpl_toolkits/tests/test_axes_grid1.py,sha256=mHdjXAd-3X1OEW0yS9iap1i2GUuvqtH4-UnnOICT_U4,15028
|
||||
mpl_toolkits/tests/test_axisartist_angle_helper.py,sha256=2jLmTrH4fw3Xty2CwaBsRJISb8qxWWn8ALRa4HL2FQA,5702
|
||||
mpl_toolkits/tests/test_axisartist_axis_artist.py,sha256=h8UXVxnt-fsfvjEOLxnyrwA4z0b7Lf-1UtvIh_SNaI4,2893
|
||||
mpl_toolkits/tests/test_axisartist_axislines.py,sha256=nzxykaFzR1XaQ7KlJNf9VNDXoXRQY4BZKuH_iIyiCHk,2266
|
||||
mpl_toolkits/tests/test_axisartist_clip_path.py,sha256=7K1Y-2DPbDdyvpAHq3XEDaXfsikw-u8v7olwaqwZ53o,1054
|
||||
mpl_toolkits/tests/test_axisartist_floating_axes.py,sha256=bTaH-fTMJum4DAjH5h4t-hH3W5BNNw9LuiWPaC24j6Q,4165
|
||||
mpl_toolkits/tests/test_axisartist_grid_finder.py,sha256=e65sLudWFIXeU08Sis3_SI1JEI6eq8YqKj-80F_Nohk,325
|
||||
mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py,sha256=TpV3ShQOPAk57KV-97dWfpULRws0zyAOQwn3JpejTe4,7487
|
||||
mpl_toolkits/tests/test_mplot3d.py,sha256=BUvMUV5OkJskzLZ1KBIIFgKIWoiWaw38FOYBJT0_xRM,26306
|
||||
pylab.py,sha256=u_By3CHla-rBMg57egFXIxZ3P_J6zEkSu_dNpBcH5pw,90
|
||||
@@ -1,5 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.31.1)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp36-cp36m-manylinux1_x86_64
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
mpl_toolkits
|
||||
mpl_toolkits
|
||||
@@ -1,3 +0,0 @@
|
||||
matplotlib
|
||||
mpl_toolkits
|
||||
pylab
|
||||
@@ -1,210 +0,0 @@
|
||||
# Javascript template for HTMLWriter
|
||||
JS_INCLUDE = """
|
||||
<link rel="stylesheet"
|
||||
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/
|
||||
css/font-awesome.min.css">
|
||||
<script language="javascript">
|
||||
/* Define the Animation class */
|
||||
function Animation(frames, img_id, slider_id, interval, loop_select_id){
|
||||
this.img_id = img_id;
|
||||
this.slider_id = slider_id;
|
||||
this.loop_select_id = loop_select_id;
|
||||
this.interval = interval;
|
||||
this.current_frame = 0;
|
||||
this.direction = 0;
|
||||
this.timer = null;
|
||||
this.frames = new Array(frames.length);
|
||||
|
||||
for (var i=0; i<frames.length; i++)
|
||||
{
|
||||
this.frames[i] = new Image();
|
||||
this.frames[i].src = frames[i];
|
||||
}
|
||||
document.getElementById(this.slider_id).max = this.frames.length - 1;
|
||||
this.set_frame(this.current_frame);
|
||||
}
|
||||
|
||||
Animation.prototype.get_loop_state = function(){
|
||||
var button_group = document[this.loop_select_id].state;
|
||||
for (var i = 0; i < button_group.length; i++) {
|
||||
var button = button_group[i];
|
||||
if (button.checked) {
|
||||
return button.value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Animation.prototype.set_frame = function(frame){
|
||||
this.current_frame = frame;
|
||||
document.getElementById(this.img_id).src =
|
||||
this.frames[this.current_frame].src;
|
||||
document.getElementById(this.slider_id).value = this.current_frame;
|
||||
}
|
||||
|
||||
Animation.prototype.next_frame = function()
|
||||
{
|
||||
this.set_frame(Math.min(this.frames.length - 1, this.current_frame + 1));
|
||||
}
|
||||
|
||||
Animation.prototype.previous_frame = function()
|
||||
{
|
||||
this.set_frame(Math.max(0, this.current_frame - 1));
|
||||
}
|
||||
|
||||
Animation.prototype.first_frame = function()
|
||||
{
|
||||
this.set_frame(0);
|
||||
}
|
||||
|
||||
Animation.prototype.last_frame = function()
|
||||
{
|
||||
this.set_frame(this.frames.length - 1);
|
||||
}
|
||||
|
||||
Animation.prototype.slower = function()
|
||||
{
|
||||
this.interval /= 0.7;
|
||||
if(this.direction > 0){this.play_animation();}
|
||||
else if(this.direction < 0){this.reverse_animation();}
|
||||
}
|
||||
|
||||
Animation.prototype.faster = function()
|
||||
{
|
||||
this.interval *= 0.7;
|
||||
if(this.direction > 0){this.play_animation();}
|
||||
else if(this.direction < 0){this.reverse_animation();}
|
||||
}
|
||||
|
||||
Animation.prototype.anim_step_forward = function()
|
||||
{
|
||||
this.current_frame += 1;
|
||||
if(this.current_frame < this.frames.length){
|
||||
this.set_frame(this.current_frame);
|
||||
}else{
|
||||
var loop_state = this.get_loop_state();
|
||||
if(loop_state == "loop"){
|
||||
this.first_frame();
|
||||
}else if(loop_state == "reflect"){
|
||||
this.last_frame();
|
||||
this.reverse_animation();
|
||||
}else{
|
||||
this.pause_animation();
|
||||
this.last_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Animation.prototype.anim_step_reverse = function()
|
||||
{
|
||||
this.current_frame -= 1;
|
||||
if(this.current_frame >= 0){
|
||||
this.set_frame(this.current_frame);
|
||||
}else{
|
||||
var loop_state = this.get_loop_state();
|
||||
if(loop_state == "loop"){
|
||||
this.last_frame();
|
||||
}else if(loop_state == "reflect"){
|
||||
this.first_frame();
|
||||
this.play_animation();
|
||||
}else{
|
||||
this.pause_animation();
|
||||
this.first_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Animation.prototype.pause_animation = function()
|
||||
{
|
||||
this.direction = 0;
|
||||
if (this.timer){
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
Animation.prototype.play_animation = function()
|
||||
{
|
||||
this.pause_animation();
|
||||
this.direction = 1;
|
||||
var t = this;
|
||||
if (!this.timer) this.timer = setInterval(function() {
|
||||
t.anim_step_forward();
|
||||
}, this.interval);
|
||||
}
|
||||
|
||||
Animation.prototype.reverse_animation = function()
|
||||
{
|
||||
this.pause_animation();
|
||||
this.direction = -1;
|
||||
var t = this;
|
||||
if (!this.timer) this.timer = setInterval(function() {
|
||||
t.anim_step_reverse();
|
||||
}, this.interval);
|
||||
}
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
# HTML template for HTMLWriter
|
||||
DISPLAY_TEMPLATE = """
|
||||
<div class="animation" align="center">
|
||||
<img id="_anim_img{id}">
|
||||
<br>
|
||||
<input id="_anim_slider{id}" type="range" style="width:350px"
|
||||
name="points" min="0" max="1" step="1" value="0"
|
||||
onchange="anim{id}.set_frame(parseInt(this.value));"></input>
|
||||
<br>
|
||||
<button onclick="anim{id}.slower()"><i class="fa fa-minus"></i></button>
|
||||
<button onclick="anim{id}.first_frame()"><i class="fa fa-fast-backward">
|
||||
</i></button>
|
||||
<button onclick="anim{id}.previous_frame()">
|
||||
<i class="fa fa-step-backward"></i></button>
|
||||
<button onclick="anim{id}.reverse_animation()">
|
||||
<i class="fa fa-play fa-flip-horizontal"></i></button>
|
||||
<button onclick="anim{id}.pause_animation()"><i class="fa fa-pause">
|
||||
</i></button>
|
||||
<button onclick="anim{id}.play_animation()"><i class="fa fa-play"></i>
|
||||
</button>
|
||||
<button onclick="anim{id}.next_frame()"><i class="fa fa-step-forward">
|
||||
</i></button>
|
||||
<button onclick="anim{id}.last_frame()"><i class="fa fa-fast-forward">
|
||||
</i></button>
|
||||
<button onclick="anim{id}.faster()"><i class="fa fa-plus"></i></button>
|
||||
<form action="#n" name="_anim_loop_select{id}" class="anim_control">
|
||||
<input type="radio" name="state"
|
||||
value="once" {once_checked}> Once </input>
|
||||
<input type="radio" name="state"
|
||||
value="loop" {loop_checked}> Loop </input>
|
||||
<input type="radio" name="state"
|
||||
value="reflect" {reflect_checked}> Reflect </input>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<script language="javascript">
|
||||
/* Instantiate the Animation class. */
|
||||
/* The IDs given should match those used in the template above. */
|
||||
(function() {{
|
||||
var img_id = "_anim_img{id}";
|
||||
var slider_id = "_anim_slider{id}";
|
||||
var loop_select_id = "_anim_loop_select{id}";
|
||||
var frames = new Array({Nframes});
|
||||
{fill_frames}
|
||||
|
||||
/* set a timeout to make sure all the above elements are created before
|
||||
the object is initialized. */
|
||||
setTimeout(function() {{
|
||||
anim{id} = new Animation(frames, img_id, slider_id, {interval},
|
||||
loop_select_id);
|
||||
}}, 0);
|
||||
}})()
|
||||
</script>
|
||||
"""
|
||||
|
||||
INCLUDED_FRAMES = """
|
||||
for (var i=0; i<{Nframes}; i++){{
|
||||
frames[i] = "{frame_dir}/frame" + ("0000000" + i).slice(-7) +
|
||||
".{frame_format}";
|
||||
}}
|
||||
"""
|
||||
@@ -1,729 +0,0 @@
|
||||
"""
|
||||
This module provides the routine to adjust subplot layouts so that there are
|
||||
no overlapping axes or axes decorations. All axes decorations are dealt with
|
||||
(labels, ticks, titles, ticklabels) and some dependent artists are also dealt
|
||||
with (colorbar, suptitle, legend).
|
||||
|
||||
Layout is done via :meth:`~matplotlib.gridspec`, with one constraint per
|
||||
gridspec, so it is possible to have overlapping axes if the gridspecs
|
||||
overlap (i.e. using :meth:`~matplotlib.gridspec.GridSpecFromSubplotSpec`).
|
||||
Axes placed using ``figure.subplots()`` or ``figure.add_subplots()`` will
|
||||
participate in the layout. Axes manually placed via ``figure.add_axes()``
|
||||
will not.
|
||||
|
||||
See Tutorial: :doc:`/tutorials/intermediate/constrainedlayout_guide`
|
||||
|
||||
"""
|
||||
|
||||
# Development Notes:
|
||||
|
||||
# What gets a layoutbox:
|
||||
# - figure
|
||||
# - gridspec
|
||||
# - subplotspec
|
||||
# EITHER:
|
||||
# - axes + pos for the axes (i.e. the total area taken by axis and
|
||||
# the actual "position" argument that needs to be sent to
|
||||
# ax.set_position.)
|
||||
# - The axes layout box will also encomapss the legend, and that is
|
||||
# how legends get included (axes legeneds, not figure legends)
|
||||
# - colorbars are sibblings of the axes if they are single-axes
|
||||
# colorbars
|
||||
# OR:
|
||||
# - a gridspec can be inside a subplotspec.
|
||||
# - subplotspec
|
||||
# EITHER:
|
||||
# - axes...
|
||||
# OR:
|
||||
# - gridspec... with arbitrary nesting...
|
||||
# - colorbars are siblings of the subplotspecs if they are multi-axes
|
||||
# colorbars.
|
||||
# - suptitle:
|
||||
# - right now suptitles are just stacked atop everything else in figure.
|
||||
# Could imagine suptitles being gridspec suptitles, but not implimented
|
||||
#
|
||||
# Todo: AnchoredOffsetbox connected to gridspecs or axes. This would
|
||||
# be more general way to add extra-axes annotations.
|
||||
|
||||
import numpy as np
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
from matplotlib.legend import Legend
|
||||
import matplotlib.transforms as transforms
|
||||
import matplotlib._layoutbox as layoutbox
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _in_same_column(colnum0min, colnum0max, colnumCmin, colnumCmax):
|
||||
return (colnumCmin <= colnum0min <= colnumCmax
|
||||
or colnumCmin <= colnum0max <= colnumCmax)
|
||||
|
||||
|
||||
def _in_same_row(rownum0min, rownum0max, rownumCmin, rownumCmax):
|
||||
return (rownumCmin <= rownum0min <= rownumCmax
|
||||
or rownumCmin <= rownum0max <= rownumCmax)
|
||||
|
||||
|
||||
def _axes_all_finite_sized(fig):
|
||||
"""
|
||||
helper function to make sure all axes in the
|
||||
figure have a finite width and height. If not, return False
|
||||
"""
|
||||
for ax in fig.axes:
|
||||
if ax._layoutbox is not None:
|
||||
newpos = ax._poslayoutbox.get_rect()
|
||||
if newpos[2] <= 0 or newpos[3] <= 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
######################################################
|
||||
def do_constrained_layout(fig, renderer, h_pad, w_pad,
|
||||
hspace=None, wspace=None):
|
||||
|
||||
"""
|
||||
Do the constrained_layout. Called at draw time in
|
||||
``figure.constrained_layout()``
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
|
||||
fig: Figure
|
||||
is the ``figure`` instance to do the layout in.
|
||||
|
||||
renderer: Renderer
|
||||
the renderer to use.
|
||||
|
||||
h_pad, w_pad : float
|
||||
are in figure-normalized units, and are a padding around the axes
|
||||
elements.
|
||||
|
||||
hspace, wspace : float
|
||||
are in fractions of the subplot sizes.
|
||||
|
||||
"""
|
||||
|
||||
''' Steps:
|
||||
|
||||
1. get a list of unique gridspecs in this figure. Each gridspec will be
|
||||
constrained separately.
|
||||
2. Check for gaps in the gridspecs. i.e. if not every axes slot in the
|
||||
gridspec has been filled. If empty, add a ghost axis that is made so
|
||||
that it cannot be seen (though visible=True). This is needed to make
|
||||
a blank spot in the layout.
|
||||
3. Compare the tight_bbox of each axes to its `position`, and assume that
|
||||
the difference is the space needed by the elements around the edge of
|
||||
the axes (decorations) like the title, ticklabels, x-labels, etc. This
|
||||
can include legends who overspill the axes boundaries.
|
||||
4. Constrain gridspec elements to line up:
|
||||
a) if colnum0 neq colnumC, the two subplotspecs are stacked next to
|
||||
each other, with the appropriate order.
|
||||
b) if colnum0 == columnC line up the left or right side of the
|
||||
_poslayoutbox (depending if it is the min or max num that is equal).
|
||||
c) do the same for rows...
|
||||
5. The above doesn't constrain relative sizes of the _poslayoutboxes at
|
||||
all, and indeed zero-size is a solution that the solver often finds more
|
||||
convenient than expanding the sizes. Right now the solution is to compare
|
||||
subplotspec sizes (i.e. drowsC and drows0) and constrain the larger
|
||||
_poslayoutbox to be larger than the ratio of the sizes. i.e. if drows0 >
|
||||
drowsC, then ax._poslayoutbox > axc._poslayoutbox * drowsC / drows0. This
|
||||
works fine *if* the decorations are similar between the axes. If the
|
||||
larger subplotspec has much larger axes decorations, then the constraint
|
||||
above is incorrect.
|
||||
|
||||
We need the greater than in the above, in general, rather than an equals
|
||||
sign. Consider the case of the left column having 2 rows, and the right
|
||||
column having 1 row. We want the top and bottom of the _poslayoutboxes to
|
||||
line up. So that means if there are decorations on the left column axes
|
||||
they will be smaller than half as large as the right hand axis.
|
||||
|
||||
This can break down if the decoration size for the right hand axis (the
|
||||
margins) is very large. There must be a math way to check for this case.
|
||||
|
||||
'''
|
||||
|
||||
invTransFig = fig.transFigure.inverted().transform_bbox
|
||||
|
||||
# list of unique gridspecs that contain child axes:
|
||||
gss = set()
|
||||
for ax in fig.axes:
|
||||
if hasattr(ax, 'get_subplotspec'):
|
||||
gs = ax.get_subplotspec().get_gridspec()
|
||||
if gs._layoutbox is not None:
|
||||
gss.add(gs)
|
||||
if len(gss) == 0:
|
||||
warnings.warn('There are no gridspecs with layoutboxes. '
|
||||
'Possibly did not call parent GridSpec with the figure= '
|
||||
'keyword')
|
||||
|
||||
if fig._layoutbox.constrained_layout_called < 1:
|
||||
for gs in gss:
|
||||
# fill in any empty gridspec slots w/ ghost axes...
|
||||
_make_ghost_gridspec_slots(fig, gs)
|
||||
|
||||
for nnn in range(2):
|
||||
# do the algrithm twice. This has to be done because decorators
|
||||
# change size after the first re-position (i.e. x/yticklabels get
|
||||
# larger/smaller). This second reposition tends to be much milder,
|
||||
# so doing twice makes things work OK.
|
||||
for ax in fig.axes:
|
||||
_log.debug(ax._layoutbox)
|
||||
if ax._layoutbox is not None:
|
||||
# make margins for each layout box based on the size of
|
||||
# the decorators.
|
||||
_make_layout_margins(ax, renderer, h_pad, w_pad)
|
||||
|
||||
# do layout for suptitle.
|
||||
if fig._suptitle is not None and fig._suptitle._layoutbox is not None:
|
||||
sup = fig._suptitle
|
||||
bbox = invTransFig(sup.get_window_extent(renderer=renderer))
|
||||
height = bbox.y1 - bbox.y0
|
||||
sup._layoutbox.edit_height(height+h_pad)
|
||||
|
||||
# OK, the above lines up ax._poslayoutbox with ax._layoutbox
|
||||
# now we need to
|
||||
# 1) arrange the subplotspecs. We do it at this level because
|
||||
# the subplotspecs are meant to contain other dependent axes
|
||||
# like colorbars or legends.
|
||||
# 2) line up the right and left side of the ax._poslayoutbox
|
||||
# that have the same subplotspec maxes.
|
||||
|
||||
if fig._layoutbox.constrained_layout_called < 1:
|
||||
# arrange the subplotspecs... This is all done relative to each
|
||||
# other. Some subplotspecs conatain axes, and others contain
|
||||
# gridspecs the ones that contain gridspecs are a set proportion
|
||||
# of their parent gridspec. The ones that contain axes are
|
||||
# not so constrained.
|
||||
figlb = fig._layoutbox
|
||||
for child in figlb.children:
|
||||
if child._is_gridspec_layoutbox():
|
||||
# This routine makes all the subplot spec containers
|
||||
# have the correct arrangement. It just stacks the
|
||||
# subplot layoutboxes in the correct order...
|
||||
_arrange_subplotspecs(child, hspace=hspace, wspace=wspace)
|
||||
|
||||
for gs in gss:
|
||||
_align_spines(fig, gs)
|
||||
|
||||
fig._layoutbox.constrained_layout_called += 1
|
||||
fig._layoutbox.update_variables()
|
||||
|
||||
# check if any axes collapsed to zero. If not, don't change positions:
|
||||
if _axes_all_finite_sized(fig):
|
||||
# Now set the position of the axes...
|
||||
for ax in fig.axes:
|
||||
if ax._layoutbox is not None:
|
||||
newpos = ax._poslayoutbox.get_rect()
|
||||
# Now set the new position.
|
||||
# ax.set_position will zero out the layout for
|
||||
# this axis, allowing users to hard-code the position,
|
||||
# so this does the same w/o zeroing layout.
|
||||
ax._set_position(newpos, which='original')
|
||||
else:
|
||||
warnings.warn('constrained_layout not applied. At least '
|
||||
'one axes collapsed to zero width or height.')
|
||||
|
||||
|
||||
def _make_ghost_gridspec_slots(fig, gs):
|
||||
"""
|
||||
Check for unoccupied gridspec slots and make ghost axes for these
|
||||
slots... Do for each gs separately. This is a pretty big kludge
|
||||
but shoudn't have too much ill effect. The worst is that
|
||||
someone querrying the figure will wonder why there are more
|
||||
axes than they thought.
|
||||
"""
|
||||
nrows, ncols = gs.get_geometry()
|
||||
hassubplotspec = np.zeros(nrows * ncols, dtype=bool)
|
||||
axs = []
|
||||
for ax in fig.axes:
|
||||
if (hasattr(ax, 'get_subplotspec')
|
||||
and ax._layoutbox is not None
|
||||
and ax.get_subplotspec().get_gridspec() == gs):
|
||||
axs += [ax]
|
||||
for ax in axs:
|
||||
ss0 = ax.get_subplotspec()
|
||||
if ss0.num2 is None:
|
||||
ss0.num2 = ss0.num1
|
||||
hassubplotspec[ss0.num1:(ss0.num2 + 1)] = True
|
||||
for nn, hss in enumerate(hassubplotspec):
|
||||
if not hss:
|
||||
# this gridspec slot doesn't have an axis so we
|
||||
# make a "ghost".
|
||||
ax = fig.add_subplot(gs[nn])
|
||||
ax.set_frame_on(False)
|
||||
ax.set_xticks([])
|
||||
ax.set_yticks([])
|
||||
ax.set_facecolor((1, 0, 0, 0))
|
||||
|
||||
|
||||
def _make_layout_margins(ax, renderer, h_pad, w_pad):
|
||||
"""
|
||||
For each axes, make a margin between the *pos* layoutbox and the
|
||||
*axes* layoutbox be a minimum size that can accommodate the
|
||||
decorations on the axis.
|
||||
"""
|
||||
fig = ax.figure
|
||||
invTransFig = fig.transFigure.inverted().transform_bbox
|
||||
|
||||
pos = ax.get_position(original=True)
|
||||
tightbbox = ax.get_tightbbox(renderer=renderer)
|
||||
bbox = invTransFig(tightbbox)
|
||||
# use stored h_pad if it exists
|
||||
h_padt = ax._poslayoutbox.h_pad
|
||||
if h_padt is None:
|
||||
h_padt = h_pad
|
||||
w_padt = ax._poslayoutbox.w_pad
|
||||
if w_padt is None:
|
||||
w_padt = w_pad
|
||||
ax._poslayoutbox.edit_left_margin_min(-bbox.x0 +
|
||||
pos.x0 + w_padt)
|
||||
ax._poslayoutbox.edit_right_margin_min(bbox.x1 -
|
||||
pos.x1 + w_padt)
|
||||
ax._poslayoutbox.edit_bottom_margin_min(
|
||||
-bbox.y0 + pos.y0 + h_padt)
|
||||
ax._poslayoutbox.edit_top_margin_min(bbox.y1-pos.y1+h_padt)
|
||||
_log.debug('left %f', (-bbox.x0 + pos.x0 + w_pad))
|
||||
_log.debug('right %f', (bbox.x1 - pos.x1 + w_pad))
|
||||
_log.debug('bottom %f', (-bbox.y0 + pos.y0 + h_padt))
|
||||
# Sometimes its possible for the solver to collapse
|
||||
# rather than expand axes, so they all have zero height
|
||||
# or width. This stops that... It *should* have been
|
||||
# taken into account w/ pref_width...
|
||||
if fig._layoutbox.constrained_layout_called < 1:
|
||||
ax._poslayoutbox.constrain_height_min(20, strength='weak')
|
||||
ax._poslayoutbox.constrain_width_min(20, strength='weak')
|
||||
ax._layoutbox.constrain_height_min(20, strength='weak')
|
||||
ax._layoutbox.constrain_width_min(20, strength='weak')
|
||||
ax._poslayoutbox.constrain_top_margin(0, strength='weak')
|
||||
ax._poslayoutbox.constrain_bottom_margin(0,
|
||||
strength='weak')
|
||||
ax._poslayoutbox.constrain_right_margin(0, strength='weak')
|
||||
ax._poslayoutbox.constrain_left_margin(0, strength='weak')
|
||||
|
||||
|
||||
def _align_spines(fig, gs):
|
||||
"""
|
||||
- Align right/left and bottom/top spines of appropriate subplots.
|
||||
- Compare size of subplotspec including height and width ratios
|
||||
and make sure that the axes spines are at least as large
|
||||
as they should be.
|
||||
"""
|
||||
# for each gridspec...
|
||||
nrows, ncols = gs.get_geometry()
|
||||
width_ratios = gs.get_width_ratios()
|
||||
height_ratios = gs.get_height_ratios()
|
||||
if width_ratios is None:
|
||||
width_ratios = np.ones(ncols)
|
||||
if height_ratios is None:
|
||||
height_ratios = np.ones(nrows)
|
||||
|
||||
# get axes in this gridspec....
|
||||
axs = []
|
||||
for ax in fig.axes:
|
||||
if (hasattr(ax, 'get_subplotspec')
|
||||
and ax._layoutbox is not None):
|
||||
if ax.get_subplotspec().get_gridspec() == gs:
|
||||
axs += [ax]
|
||||
rownummin = np.zeros(len(axs), dtype=np.int8)
|
||||
rownummax = np.zeros(len(axs), dtype=np.int8)
|
||||
colnummin = np.zeros(len(axs), dtype=np.int8)
|
||||
colnummax = np.zeros(len(axs), dtype=np.int8)
|
||||
width = np.zeros(len(axs))
|
||||
height = np.zeros(len(axs))
|
||||
|
||||
for n, ax in enumerate(axs):
|
||||
ss0 = ax.get_subplotspec()
|
||||
if ss0.num2 is None:
|
||||
ss0.num2 = ss0.num1
|
||||
rownummin[n], colnummin[n] = divmod(ss0.num1, ncols)
|
||||
rownummax[n], colnummax[n] = divmod(ss0.num2, ncols)
|
||||
width[n] = np.sum(
|
||||
width_ratios[colnummin[n]:(colnummax[n] + 1)])
|
||||
height[n] = np.sum(
|
||||
height_ratios[rownummin[n]:(rownummax[n] + 1)])
|
||||
|
||||
for nn, ax in enumerate(axs[:-1]):
|
||||
ss0 = ax.get_subplotspec()
|
||||
|
||||
# now compare ax to all the axs:
|
||||
#
|
||||
# If the subplotspecs have the same colnumXmax, then line
|
||||
# up their right sides. If they have the same min, then
|
||||
# line up their left sides (and vertical equivalents).
|
||||
rownum0min, colnum0min = rownummin[nn], colnummin[nn]
|
||||
rownum0max, colnum0max = rownummax[nn], colnummax[nn]
|
||||
width0, height0 = width[nn], height[nn]
|
||||
alignleft = False
|
||||
alignright = False
|
||||
alignbot = False
|
||||
aligntop = False
|
||||
alignheight = False
|
||||
alignwidth = False
|
||||
for mm in range(nn+1, len(axs)):
|
||||
axc = axs[mm]
|
||||
rownumCmin, colnumCmin = rownummin[mm], colnummin[mm]
|
||||
rownumCmax, colnumCmax = rownummax[mm], colnummax[mm]
|
||||
widthC, heightC = width[mm], height[mm]
|
||||
# Horizontally align axes spines if they have the
|
||||
# same min or max:
|
||||
if not alignleft and colnum0min == colnumCmin:
|
||||
# we want the _poslayoutboxes to line up on left
|
||||
# side of the axes spines...
|
||||
layoutbox.align([ax._poslayoutbox,
|
||||
axc._poslayoutbox],
|
||||
'left')
|
||||
alignleft = True
|
||||
|
||||
if not alignright and colnum0max == colnumCmax:
|
||||
# line up right sides of _poslayoutbox
|
||||
layoutbox.align([ax._poslayoutbox,
|
||||
axc._poslayoutbox],
|
||||
'right')
|
||||
alignright = True
|
||||
# Vertically align axes spines if they have the
|
||||
# same min or max:
|
||||
if not aligntop and rownum0min == rownumCmin:
|
||||
# line up top of _poslayoutbox
|
||||
_log.debug('rownum0min == rownumCmin')
|
||||
layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
|
||||
'top')
|
||||
aligntop = True
|
||||
|
||||
if not alignbot and rownum0max == rownumCmax:
|
||||
# line up bottom of _poslayoutbox
|
||||
_log.debug('rownum0max == rownumCmax')
|
||||
layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
|
||||
'bottom')
|
||||
alignbot = True
|
||||
###########
|
||||
# Now we make the widths and heights of position boxes
|
||||
# similar. (i.e the spine locations)
|
||||
# This allows vertically stacked subplots to have
|
||||
# different sizes if they occupy different amounts
|
||||
# of the gridspec: i.e.
|
||||
# gs = gridspec.GridSpec(3,1)
|
||||
# ax1 = gs[0,:]
|
||||
# ax2 = gs[1:,:]
|
||||
# then drows0 = 1, and drowsC = 2, and ax2
|
||||
# should be at least twice as large as ax1.
|
||||
# But it can be more than twice as large because
|
||||
# it needs less room for the labeling.
|
||||
#
|
||||
# For height, this only needs to be done if the
|
||||
# subplots share a column. For width if they
|
||||
# share a row.
|
||||
|
||||
drowsC = (rownumCmax - rownumCmin + 1)
|
||||
drows0 = (rownum0max - rownum0min + 1)
|
||||
dcolsC = (colnumCmax - colnumCmin + 1)
|
||||
dcols0 = (colnum0max - colnum0min + 1)
|
||||
|
||||
if not alignheight and drows0 == drowsC:
|
||||
ax._poslayoutbox.constrain_height(
|
||||
axc._poslayoutbox.height * height0 / heightC)
|
||||
alignheight = True
|
||||
elif _in_same_column(colnum0min, colnum0max,
|
||||
colnumCmin, colnumCmax):
|
||||
if height0 > heightC:
|
||||
ax._poslayoutbox.constrain_height_min(
|
||||
axc._poslayoutbox.height * height0 / heightC)
|
||||
# these constraints stop the smaller axes from
|
||||
# being allowed to go to zero height...
|
||||
axc._poslayoutbox.constrain_height_min(
|
||||
ax._poslayoutbox.height * heightC /
|
||||
(height0*1.8))
|
||||
elif height0 < heightC:
|
||||
axc._poslayoutbox.constrain_height_min(
|
||||
ax._poslayoutbox.height * heightC / height0)
|
||||
ax._poslayoutbox.constrain_height_min(
|
||||
ax._poslayoutbox.height * height0 /
|
||||
(heightC*1.8))
|
||||
# widths...
|
||||
if not alignwidth and dcols0 == dcolsC:
|
||||
ax._poslayoutbox.constrain_width(
|
||||
axc._poslayoutbox.width * width0 / widthC)
|
||||
alignwidth = True
|
||||
elif _in_same_row(rownum0min, rownum0max,
|
||||
rownumCmin, rownumCmax):
|
||||
if width0 > widthC:
|
||||
ax._poslayoutbox.constrain_width_min(
|
||||
axc._poslayoutbox.width * width0 / widthC)
|
||||
axc._poslayoutbox.constrain_width_min(
|
||||
ax._poslayoutbox.width * widthC /
|
||||
(width0*1.8))
|
||||
elif width0 < widthC:
|
||||
axc._poslayoutbox.constrain_width_min(
|
||||
ax._poslayoutbox.width * widthC / width0)
|
||||
ax._poslayoutbox.constrain_width_min(
|
||||
axc._poslayoutbox.width * width0 /
|
||||
(widthC*1.8))
|
||||
|
||||
|
||||
def _arrange_subplotspecs(gs, hspace=0, wspace=0):
|
||||
"""
|
||||
arrange the subplotspec children of this gridspec, and then recursively
|
||||
do the same of any gridspec children of those gridspecs...
|
||||
"""
|
||||
sschildren = []
|
||||
for child in gs.children:
|
||||
if child._is_subplotspec_layoutbox():
|
||||
for child2 in child.children:
|
||||
# check for gridspec children...
|
||||
if child2._is_gridspec_layoutbox():
|
||||
_arrange_subplotspecs(child2, hspace=hspace, wspace=wspace)
|
||||
sschildren += [child]
|
||||
# now arrange the subplots...
|
||||
for child0 in sschildren:
|
||||
ss0 = child0.artist
|
||||
nrows, ncols = ss0.get_gridspec().get_geometry()
|
||||
if ss0.num2 is None:
|
||||
ss0.num2 = ss0.num1
|
||||
rowNum0min, colNum0min = divmod(ss0.num1, ncols)
|
||||
rowNum0max, colNum0max = divmod(ss0.num2, ncols)
|
||||
sschildren = sschildren[1:]
|
||||
for childc in sschildren:
|
||||
ssc = childc.artist
|
||||
rowNumCmin, colNumCmin = divmod(ssc.num1, ncols)
|
||||
if ssc.num2 is None:
|
||||
ssc.num2 = ssc.num1
|
||||
rowNumCmax, colNumCmax = divmod(ssc.num2, ncols)
|
||||
# OK, this tells us the relative layout of ax
|
||||
# with axc
|
||||
thepad = wspace / ncols
|
||||
if colNum0max < colNumCmin:
|
||||
layoutbox.hstack([ss0._layoutbox, ssc._layoutbox],
|
||||
padding=thepad)
|
||||
if colNumCmax < colNum0min:
|
||||
layoutbox.hstack([ssc._layoutbox, ss0._layoutbox],
|
||||
padding=thepad)
|
||||
|
||||
####
|
||||
# vertical alignment
|
||||
thepad = hspace / nrows
|
||||
if rowNum0max < rowNumCmin:
|
||||
layoutbox.vstack([ss0._layoutbox,
|
||||
ssc._layoutbox],
|
||||
padding=thepad)
|
||||
if rowNumCmax < rowNum0min:
|
||||
layoutbox.vstack([ssc._layoutbox,
|
||||
ss0._layoutbox],
|
||||
padding=thepad)
|
||||
|
||||
|
||||
def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
|
||||
"""
|
||||
Do the layout for a colorbar, to not oeverly pollute colorbar.py
|
||||
|
||||
`pad` is in fraction of the original axis size.
|
||||
"""
|
||||
axlb = ax._layoutbox
|
||||
axpos = ax._poslayoutbox
|
||||
axsslb = ax.get_subplotspec()._layoutbox
|
||||
lb = layoutbox.LayoutBox(
|
||||
parent=axsslb,
|
||||
name=axsslb.name + '.cbar',
|
||||
artist=cax)
|
||||
|
||||
if location in ('left', 'right'):
|
||||
lbpos = layoutbox.LayoutBox(
|
||||
parent=lb,
|
||||
name=lb.name + '.pos',
|
||||
tightwidth=False,
|
||||
pos=True,
|
||||
subplot=False,
|
||||
artist=cax)
|
||||
|
||||
if location == 'right':
|
||||
# arrange to right of parent axis
|
||||
layoutbox.hstack([axlb, lb], padding=pad * axlb.width,
|
||||
strength='strong')
|
||||
else:
|
||||
layoutbox.hstack([lb, axlb], padding=pad * axlb.width)
|
||||
# constrain the height and center...
|
||||
layoutbox.match_heights([axpos, lbpos], [1, shrink])
|
||||
layoutbox.align([axpos, lbpos], 'v_center')
|
||||
# set the width of the pos box
|
||||
lbpos.constrain_width(shrink * axpos.height * (1/aspect),
|
||||
strength='strong')
|
||||
elif location in ('bottom', 'top'):
|
||||
lbpos = layoutbox.LayoutBox(
|
||||
parent=lb,
|
||||
name=lb.name + '.pos',
|
||||
tightheight=True,
|
||||
pos=True,
|
||||
subplot=False,
|
||||
artist=cax)
|
||||
|
||||
if location == 'bottom':
|
||||
layoutbox.vstack([axlb, lb], padding=pad * axlb.height)
|
||||
else:
|
||||
layoutbox.vstack([lb, axlb], padding=pad * axlb.height)
|
||||
# constrain the height and center...
|
||||
layoutbox.match_widths([axpos, lbpos],
|
||||
[1, shrink], strength='strong')
|
||||
layoutbox.align([axpos, lbpos], 'h_center')
|
||||
# set the height of the pos box
|
||||
lbpos.constrain_height(axpos.width * aspect * shrink,
|
||||
strength='medium')
|
||||
|
||||
return lb, lbpos
|
||||
|
||||
|
||||
def _getmaxminrowcolumn(axs):
|
||||
# helper to get the min/max rows and columns of a list of axes.
|
||||
maxrow = -100000
|
||||
minrow = 1000000
|
||||
maxax = None
|
||||
minax = None
|
||||
maxcol = -100000
|
||||
mincol = 1000000
|
||||
maxax_col = None
|
||||
minax_col = None
|
||||
|
||||
for ax in axs:
|
||||
subspec = ax.get_subplotspec()
|
||||
nrows, ncols, row_start, row_stop, col_start, col_stop = \
|
||||
subspec.get_rows_columns()
|
||||
if row_stop > maxrow:
|
||||
maxrow = row_stop
|
||||
maxax = ax
|
||||
if row_start < minrow:
|
||||
minrow = row_start
|
||||
minax = ax
|
||||
if col_stop > maxcol:
|
||||
maxcol = col_stop
|
||||
maxax_col = ax
|
||||
if col_start < mincol:
|
||||
mincol = col_start
|
||||
minax_col = ax
|
||||
return (minrow, maxrow, minax, maxax, mincol, maxcol, minax_col, maxax_col)
|
||||
|
||||
|
||||
def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
|
||||
"""
|
||||
Do the layout for a colorbar, to not oeverly pollute colorbar.py
|
||||
|
||||
`pad` is in fraction of the original axis size.
|
||||
"""
|
||||
|
||||
gs = parents[0].get_subplotspec().get_gridspec()
|
||||
# parent layout box....
|
||||
gslb = gs._layoutbox
|
||||
|
||||
lb = layoutbox.LayoutBox(parent=gslb.parent,
|
||||
name=gslb.parent.name + '.cbar',
|
||||
artist=cax)
|
||||
# figure out the row and column extent of the parents.
|
||||
(minrow, maxrow, minax_row, maxax_row,
|
||||
mincol, maxcol, minax_col, maxax_col) = _getmaxminrowcolumn(parents)
|
||||
|
||||
if location in ('left', 'right'):
|
||||
lbpos = layoutbox.LayoutBox(
|
||||
parent=lb,
|
||||
name=lb.name + '.pos',
|
||||
tightwidth=False,
|
||||
pos=True,
|
||||
subplot=False,
|
||||
artist=cax)
|
||||
for ax in parents:
|
||||
if location == 'right':
|
||||
order = [ax._layoutbox, lb]
|
||||
else:
|
||||
order = [lb, ax._layoutbox]
|
||||
layoutbox.hstack(order, padding=pad * gslb.width,
|
||||
strength='strong')
|
||||
# constrain the height and center...
|
||||
# This isn't quite right. We'd like the colorbar
|
||||
# pos to line up w/ the axes poss, not the size of the
|
||||
# gs.
|
||||
|
||||
# Horizontal Layout: need to check all the axes in this gridspec
|
||||
for ch in gslb.children:
|
||||
subspec = ch.artist
|
||||
nrows, ncols, row_start, row_stop, col_start, col_stop = \
|
||||
subspec.get_rows_columns()
|
||||
if location == 'right':
|
||||
if col_stop <= maxcol:
|
||||
order = [subspec._layoutbox, lb]
|
||||
# arrange to right of the parents
|
||||
if col_start > maxcol:
|
||||
order = [lb, subspec._layoutbox]
|
||||
elif location == 'left':
|
||||
if col_start >= mincol:
|
||||
order = [lb, subspec._layoutbox]
|
||||
if col_stop < mincol:
|
||||
order = [subspec._layoutbox, lb]
|
||||
layoutbox.hstack(order, padding=pad * gslb.width,
|
||||
strength='strong')
|
||||
|
||||
# Vertical layout:
|
||||
maxposlb = minax_row._poslayoutbox
|
||||
minposlb = maxax_row._poslayoutbox
|
||||
# now we want the height of the colorbar pos to be
|
||||
# set by the top and bottom of the min/max axes...
|
||||
# bottom top
|
||||
# b t
|
||||
# h = (top-bottom)*shrink
|
||||
# b = bottom + (top-bottom - h) / 2.
|
||||
lbpos.constrain_height(
|
||||
(maxposlb.top - minposlb.bottom) *
|
||||
shrink, strength='strong')
|
||||
lbpos.constrain_bottom(
|
||||
(maxposlb.top - minposlb.bottom) *
|
||||
(1 - shrink)/2 + minposlb.bottom,
|
||||
strength='strong')
|
||||
|
||||
# set the width of the pos box
|
||||
lbpos.constrain_width(lbpos.height * (shrink / aspect),
|
||||
strength='strong')
|
||||
elif location in ('bottom', 'top'):
|
||||
lbpos = layoutbox.LayoutBox(
|
||||
parent=lb,
|
||||
name=lb.name + '.pos',
|
||||
tightheight=True,
|
||||
pos=True,
|
||||
subplot=False,
|
||||
artist=cax)
|
||||
|
||||
for ax in parents:
|
||||
if location == 'bottom':
|
||||
order = [ax._layoutbox, lb]
|
||||
else:
|
||||
order = [lb, ax._layoutbox]
|
||||
layoutbox.vstack(order, padding=pad * gslb.width,
|
||||
strength='strong')
|
||||
|
||||
# Vertical Layout: need to check all the axes in this gridspec
|
||||
for ch in gslb.children:
|
||||
subspec = ch.artist
|
||||
nrows, ncols, row_start, row_stop, col_start, col_stop = \
|
||||
subspec.get_rows_columns()
|
||||
if location == 'bottom':
|
||||
if row_stop <= minrow:
|
||||
order = [subspec._layoutbox, lb]
|
||||
if row_start > maxrow:
|
||||
order = [lb, subspec._layoutbox]
|
||||
elif location == 'top':
|
||||
if row_stop < minrow:
|
||||
order = [subspec._layoutbox, lb]
|
||||
if row_start >= maxrow:
|
||||
order = [lb, subspec._layoutbox]
|
||||
layoutbox.vstack(order, padding=pad * gslb.width,
|
||||
strength='strong')
|
||||
|
||||
# Do horizontal layout...
|
||||
maxposlb = maxax_col._poslayoutbox
|
||||
minposlb = minax_col._poslayoutbox
|
||||
lbpos.constrain_width((maxposlb.right - minposlb.left) *
|
||||
shrink)
|
||||
lbpos.constrain_left(
|
||||
(maxposlb.right - minposlb.left) *
|
||||
(1-shrink)/2 + minposlb.left)
|
||||
# set the height of the pos box
|
||||
lbpos.constrain_height(lbpos.width * shrink * aspect,
|
||||
strength='medium')
|
||||
|
||||
return lb, lbpos
|
||||
@@ -1,735 +0,0 @@
|
||||
"""
|
||||
|
||||
Conventions:
|
||||
|
||||
"constrain_x" means to constrain the variable with either
|
||||
another kiwisolver variable, or a float. i.e. `constrain_width(0.2)`
|
||||
will set a constraint that the width has to be 0.2 and this constraint is
|
||||
permanent - i.e. it will not be removed if it becomes obsolete.
|
||||
|
||||
"edit_x" means to set x to a value (just a float), and that this value can
|
||||
change. So `edit_width(0.2)` will set width to be 0.2, but `edit_width(0.3)`
|
||||
will allow it to change to 0.3 later. Note that these values are still just
|
||||
"suggestions" in `kiwisolver` parlance, and could be over-ridden by
|
||||
other constrains.
|
||||
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import kiwisolver as kiwi
|
||||
import logging
|
||||
import numpy as np
|
||||
import warnings
|
||||
|
||||
import matplotlib
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# renderers can be complicated
|
||||
def get_renderer(fig):
|
||||
if fig._cachedRenderer:
|
||||
renderer = fig._cachedRenderer
|
||||
else:
|
||||
canvas = fig.canvas
|
||||
if canvas and hasattr(canvas, "get_renderer"):
|
||||
renderer = canvas.get_renderer()
|
||||
else:
|
||||
# not sure if this can happen
|
||||
# seems to with PDF...
|
||||
_log.info("constrained_layout : falling back to Agg renderer")
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
||||
canvas = FigureCanvasAgg(fig)
|
||||
renderer = canvas.get_renderer()
|
||||
|
||||
return renderer
|
||||
|
||||
|
||||
class LayoutBox(object):
|
||||
"""
|
||||
Basic rectangle representation using kiwi solver variables
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None, name='', tightwidth=False,
|
||||
tightheight=False, artist=None,
|
||||
lower_left=(0, 0), upper_right=(1, 1), pos=False,
|
||||
subplot=False, h_pad=None, w_pad=None):
|
||||
Variable = kiwi.Variable
|
||||
self.parent = parent
|
||||
self.name = name
|
||||
sn = self.name + '_'
|
||||
if parent is None:
|
||||
self.solver = kiwi.Solver()
|
||||
self.constrained_layout_called = 0
|
||||
else:
|
||||
self.solver = parent.solver
|
||||
self.constrained_layout_called = None
|
||||
# parent wants to know about this child!
|
||||
parent.add_child(self)
|
||||
# keep track of artist associated w/ this layout. Can be none
|
||||
self.artist = artist
|
||||
# keep track if this box is supposed to be a pos that is constrained
|
||||
# by the parent.
|
||||
self.pos = pos
|
||||
# keep track of whether we need to match this subplot up with others.
|
||||
self.subplot = subplot
|
||||
|
||||
# we need the str below for Py 2 which complains the string is unicode
|
||||
self.top = Variable(str(sn + 'top'))
|
||||
self.bottom = Variable(str(sn + 'bottom'))
|
||||
self.left = Variable(str(sn + 'left'))
|
||||
self.right = Variable(str(sn + 'right'))
|
||||
|
||||
self.width = Variable(str(sn + 'width'))
|
||||
self.height = Variable(str(sn + 'height'))
|
||||
self.h_center = Variable(str(sn + 'h_center'))
|
||||
self.v_center = Variable(str(sn + 'v_center'))
|
||||
|
||||
self.min_width = Variable(str(sn + 'min_width'))
|
||||
self.min_height = Variable(str(sn + 'min_height'))
|
||||
self.pref_width = Variable(str(sn + 'pref_width'))
|
||||
self.pref_height = Variable(str(sn + 'pref_height'))
|
||||
# margis are only used for axes-position layout boxes. maybe should
|
||||
# be a separate subclass:
|
||||
self.left_margin = Variable(str(sn + 'left_margin'))
|
||||
self.right_margin = Variable(str(sn + 'right_margin'))
|
||||
self.bottom_margin = Variable(str(sn + 'bottom_margin'))
|
||||
self.top_margin = Variable(str(sn + 'top_margin'))
|
||||
# mins
|
||||
self.left_margin_min = Variable(str(sn + 'left_margin_min'))
|
||||
self.right_margin_min = Variable(str(sn + 'right_margin_min'))
|
||||
self.bottom_margin_min = Variable(str(sn + 'bottom_margin_min'))
|
||||
self.top_margin_min = Variable(str(sn + 'top_margin_min'))
|
||||
|
||||
right, top = upper_right
|
||||
left, bottom = lower_left
|
||||
self.tightheight = tightheight
|
||||
self.tightwidth = tightwidth
|
||||
self.add_constraints()
|
||||
self.children = []
|
||||
self.subplotspec = None
|
||||
if self.pos:
|
||||
self.constrain_margins()
|
||||
self.h_pad = h_pad
|
||||
self.w_pad = w_pad
|
||||
|
||||
def constrain_margins(self):
|
||||
"""
|
||||
Only do this for pos. This sets a variable distance
|
||||
margin between the position of the axes and the outer edge of
|
||||
the axes.
|
||||
|
||||
Margins are variable because they change with the fogure size.
|
||||
|
||||
Margin minimums are set to make room for axes decorations. However,
|
||||
the margins can be larger if we are mathicng the position size to
|
||||
otehr axes.
|
||||
"""
|
||||
sol = self.solver
|
||||
|
||||
# left
|
||||
if not sol.hasEditVariable(self.left_margin_min):
|
||||
sol.addEditVariable(self.left_margin_min, 'strong')
|
||||
sol.suggestValue(self.left_margin_min, 0.0001)
|
||||
c = (self.left_margin == self.left - self.parent.left)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
c = (self.left_margin >= self.left_margin_min)
|
||||
self.solver.addConstraint(c | 'strong')
|
||||
|
||||
# right
|
||||
if not sol.hasEditVariable(self.right_margin_min):
|
||||
sol.addEditVariable(self.right_margin_min, 'strong')
|
||||
sol.suggestValue(self.right_margin_min, 0.0001)
|
||||
c = (self.right_margin == self.parent.right - self.right)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
c = (self.right_margin >= self.right_margin_min)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
# bottom
|
||||
if not sol.hasEditVariable(self.bottom_margin_min):
|
||||
sol.addEditVariable(self.bottom_margin_min, 'strong')
|
||||
sol.suggestValue(self.bottom_margin_min, 0.0001)
|
||||
c = (self.bottom_margin == self.bottom - self.parent.bottom)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
c = (self.bottom_margin >= self.bottom_margin_min)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
# top
|
||||
if not sol.hasEditVariable(self.top_margin_min):
|
||||
sol.addEditVariable(self.top_margin_min, 'strong')
|
||||
sol.suggestValue(self.top_margin_min, 0.0001)
|
||||
c = (self.top_margin == self.parent.top - self.top)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
c = (self.top_margin >= self.top_margin_min)
|
||||
self.solver.addConstraint(c | 'required')
|
||||
|
||||
def add_child(self, child):
|
||||
self.children += [child]
|
||||
|
||||
def remove_child(self, child):
|
||||
try:
|
||||
self.children.remove(child)
|
||||
except ValueError:
|
||||
_log.info("Tried to remove child that doesn't belong to parent")
|
||||
|
||||
def add_constraints(self):
|
||||
sol = self.solver
|
||||
# never let width and height go negative.
|
||||
for i in [self.min_width, self.min_height]:
|
||||
sol.addEditVariable(i, 1e9)
|
||||
sol.suggestValue(i, 0.0)
|
||||
# define relation ships between things thing width and right and left
|
||||
self.hard_constraints()
|
||||
# self.soft_constraints()
|
||||
if self.parent:
|
||||
self.parent_constrain()
|
||||
# sol.updateVariables()
|
||||
|
||||
def parent_constrain(self):
|
||||
parent = self.parent
|
||||
hc = [self.left >= parent.left,
|
||||
self.bottom >= parent.bottom,
|
||||
self.top <= parent.top,
|
||||
self.right <= parent.right]
|
||||
for c in hc:
|
||||
self.solver.addConstraint(c | 'required')
|
||||
|
||||
def hard_constraints(self):
|
||||
hc = [self.width == self.right - self.left,
|
||||
self.height == self.top - self.bottom,
|
||||
self.h_center == (self.left + self.right) * 0.5,
|
||||
self.v_center == (self.top + self.bottom) * 0.5,
|
||||
self.width >= self.min_width,
|
||||
self.height >= self.min_height]
|
||||
for c in hc:
|
||||
self.solver.addConstraint(c | 'required')
|
||||
|
||||
def soft_constraints(self):
|
||||
sol = self.solver
|
||||
if self.tightwidth:
|
||||
suggest = 0.
|
||||
else:
|
||||
suggest = 20.
|
||||
c = (self.pref_width == suggest)
|
||||
for i in c:
|
||||
sol.addConstraint(i | 'required')
|
||||
if self.tightheight:
|
||||
suggest = 0.
|
||||
else:
|
||||
suggest = 20.
|
||||
c = (self.pref_height == suggest)
|
||||
for i in c:
|
||||
sol.addConstraint(i | 'required')
|
||||
|
||||
c = [(self.width >= suggest),
|
||||
(self.height >= suggest)]
|
||||
for i in c:
|
||||
sol.addConstraint(i | 150000)
|
||||
|
||||
def set_parent(self, parent):
|
||||
''' replace the parent of this with the new parent
|
||||
'''
|
||||
self.parent = parent
|
||||
self.parent_constrain()
|
||||
|
||||
def constrain_geometry(self, left, bottom, right, top, strength='strong'):
|
||||
hc = [self.left == left,
|
||||
self.right == right,
|
||||
self.bottom == bottom,
|
||||
self.top == top]
|
||||
for c in hc:
|
||||
self.solver.addConstraint((c | strength))
|
||||
# self.solver.updateVariables()
|
||||
|
||||
def constrain_same(self, other, strength='strong'):
|
||||
"""
|
||||
Make the layoutbox have same position as other layoutbox
|
||||
"""
|
||||
hc = [self.left == other.left,
|
||||
self.right == other.right,
|
||||
self.bottom == other.bottom,
|
||||
self.top == other.top]
|
||||
for c in hc:
|
||||
self.solver.addConstraint((c | strength))
|
||||
|
||||
def constrain_left_margin(self, margin, strength='strong'):
|
||||
c = (self.left == self.parent.left + margin)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def edit_left_margin_min(self, margin):
|
||||
self.solver.suggestValue(self.left_margin_min, margin)
|
||||
|
||||
def constrain_right_margin(self, margin, strength='strong'):
|
||||
c = (self.right == self.parent.right - margin)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def edit_right_margin_min(self, margin):
|
||||
self.solver.suggestValue(self.right_margin_min, margin)
|
||||
|
||||
def constrain_bottom_margin(self, margin, strength='strong'):
|
||||
c = (self.bottom == self.parent.bottom + margin)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def edit_bottom_margin_min(self, margin):
|
||||
self.solver.suggestValue(self.bottom_margin_min, margin)
|
||||
|
||||
def constrain_top_margin(self, margin, strength='strong'):
|
||||
c = (self.top == self.parent.top - margin)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def edit_top_margin_min(self, margin):
|
||||
self.solver.suggestValue(self.top_margin_min, margin)
|
||||
|
||||
def get_rect(self):
|
||||
return (self.left.value(), self.bottom.value(),
|
||||
self.width.value(), self.height.value())
|
||||
|
||||
def update_variables(self):
|
||||
'''
|
||||
Update *all* the variables that are part of the solver this LayoutBox
|
||||
is created with
|
||||
'''
|
||||
self.solver.updateVariables()
|
||||
|
||||
def edit_height(self, height, strength='strong'):
|
||||
'''
|
||||
Set the height of the layout box.
|
||||
|
||||
This is done as an editable variable so that the value can change
|
||||
due to resizing.
|
||||
'''
|
||||
sol = self.solver
|
||||
for i in [self.height]:
|
||||
if not sol.hasEditVariable(i):
|
||||
sol.addEditVariable(i, strength)
|
||||
sol.suggestValue(self.height, height)
|
||||
|
||||
def constrain_height(self, height, strength='strong'):
|
||||
'''
|
||||
Constrain the height of the layout box. height is
|
||||
either a float or a layoutbox.height.
|
||||
'''
|
||||
c = (self.height == height)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_height_min(self, height, strength='strong'):
|
||||
c = (self.height >= height)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def edit_width(self, width, strength='strong'):
|
||||
sol = self.solver
|
||||
for i in [self.width]:
|
||||
if not sol.hasEditVariable(i):
|
||||
sol.addEditVariable(i, strength)
|
||||
sol.suggestValue(self.width, width)
|
||||
|
||||
def constrain_width(self, width, strength='strong'):
|
||||
'''
|
||||
Constrain the width of the layout box. `width` is
|
||||
either a float or a layoutbox.width.
|
||||
'''
|
||||
c = (self.width == width)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_width_min(self, width, strength='strong'):
|
||||
c = (self.width >= width)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_left(self, left, strength='strong'):
|
||||
c = (self.left == left)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_bottom(self, bottom, strength='strong'):
|
||||
c = (self.bottom == bottom)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_right(self, right, strength='strong'):
|
||||
c = (self.right == right)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def constrain_top(self, top, strength='strong'):
|
||||
c = (self.top == top)
|
||||
self.solver.addConstraint(c | strength)
|
||||
|
||||
def _is_subplotspec_layoutbox(self):
|
||||
'''
|
||||
Helper to check if this layoutbox is the layoutbox of a
|
||||
subplotspec
|
||||
'''
|
||||
name = (self.name).split('.')[-1]
|
||||
return name[:2] == 'ss'
|
||||
|
||||
def _is_gridspec_layoutbox(self):
|
||||
'''
|
||||
Helper to check if this layoutbox is the layoutbox of a
|
||||
gridspec
|
||||
'''
|
||||
name = (self.name).split('.')[-1]
|
||||
return name[:8] == 'gridspec'
|
||||
|
||||
def find_child_subplots(self):
|
||||
'''
|
||||
Find children of this layout box that are subplots. We want to line
|
||||
poss up, and this is an easy way to find them all.
|
||||
'''
|
||||
if self.subplot:
|
||||
subplots = [self]
|
||||
else:
|
||||
subplots = []
|
||||
for child in self.children:
|
||||
subplots += child.find_child_subplots()
|
||||
return subplots
|
||||
|
||||
def layout_from_subplotspec(self, subspec,
|
||||
name='', artist=None, pos=False):
|
||||
''' Make a layout box from a subplotspec. The layout box is
|
||||
constrained to be a fraction of the width/height of the parent,
|
||||
and be a fraction of the parent width/height from the left/bottom
|
||||
of the parent. Therefore the parent can move around and the
|
||||
layout for the subplot spec should move with it.
|
||||
|
||||
The parent is *usually* the gridspec that made the subplotspec.??
|
||||
'''
|
||||
lb = LayoutBox(parent=self, name=name, artist=artist, pos=pos)
|
||||
gs = subspec.get_gridspec()
|
||||
nrows, ncols = gs.get_geometry()
|
||||
parent = self.parent
|
||||
|
||||
# OK, now, we want to set the position of this subplotspec
|
||||
# based on its subplotspec parameters. The new gridspec will inherit.
|
||||
|
||||
# from gridspec. prob should be new method in gridspec
|
||||
left = 0.0
|
||||
right = 1.0
|
||||
bottom = 0.0
|
||||
top = 1.0
|
||||
totWidth = right-left
|
||||
totHeight = top-bottom
|
||||
hspace = 0.
|
||||
wspace = 0.
|
||||
|
||||
# calculate accumulated heights of columns
|
||||
cellH = totHeight / (nrows + hspace * (nrows - 1))
|
||||
sepH = hspace*cellH
|
||||
|
||||
if gs._row_height_ratios is not None:
|
||||
netHeight = cellH * nrows
|
||||
tr = float(sum(gs._row_height_ratios))
|
||||
cellHeights = [netHeight*r/tr for r in gs._row_height_ratios]
|
||||
else:
|
||||
cellHeights = [cellH] * nrows
|
||||
|
||||
sepHeights = [0] + ([sepH] * (nrows - 1))
|
||||
cellHs = np.add.accumulate(np.ravel(
|
||||
list(zip(sepHeights, cellHeights))))
|
||||
|
||||
# calculate accumulated widths of rows
|
||||
cellW = totWidth/(ncols + wspace * (ncols - 1))
|
||||
sepW = wspace*cellW
|
||||
|
||||
if gs._col_width_ratios is not None:
|
||||
netWidth = cellW * ncols
|
||||
tr = float(sum(gs._col_width_ratios))
|
||||
cellWidths = [netWidth * r / tr for r in gs._col_width_ratios]
|
||||
else:
|
||||
cellWidths = [cellW] * ncols
|
||||
|
||||
sepWidths = [0] + ([sepW] * (ncols - 1))
|
||||
cellWs = np.add.accumulate(np.ravel(list(zip(sepWidths, cellWidths))))
|
||||
|
||||
figTops = [top - cellHs[2 * rowNum] for rowNum in range(nrows)]
|
||||
figBottoms = [top - cellHs[2 * rowNum + 1] for rowNum in range(nrows)]
|
||||
figLefts = [left + cellWs[2 * colNum] for colNum in range(ncols)]
|
||||
figRights = [left + cellWs[2 * colNum + 1] for colNum in range(ncols)]
|
||||
|
||||
rowNum, colNum = divmod(subspec.num1, ncols)
|
||||
figBottom = figBottoms[rowNum]
|
||||
figTop = figTops[rowNum]
|
||||
figLeft = figLefts[colNum]
|
||||
figRight = figRights[colNum]
|
||||
|
||||
if subspec.num2 is not None:
|
||||
|
||||
rowNum2, colNum2 = divmod(subspec.num2, ncols)
|
||||
figBottom2 = figBottoms[rowNum2]
|
||||
figTop2 = figTops[rowNum2]
|
||||
figLeft2 = figLefts[colNum2]
|
||||
figRight2 = figRights[colNum2]
|
||||
|
||||
figBottom = min(figBottom, figBottom2)
|
||||
figLeft = min(figLeft, figLeft2)
|
||||
figTop = max(figTop, figTop2)
|
||||
figRight = max(figRight, figRight2)
|
||||
# These are numbers relative to 0,0,1,1. Need to constrain
|
||||
# relative to parent.
|
||||
|
||||
width = figRight - figLeft
|
||||
height = figTop - figBottom
|
||||
parent = self.parent
|
||||
cs = [self.left == parent.left + parent.width * figLeft,
|
||||
self.bottom == parent.bottom + parent.height * figBottom,
|
||||
self.width == parent.width * width,
|
||||
self.height == parent.height * height]
|
||||
for c in cs:
|
||||
self.solver.addConstraint((c | 'required'))
|
||||
|
||||
return lb
|
||||
|
||||
def __repr__(self):
|
||||
args = (self.name, self.left.value(), self.bottom.value(),
|
||||
self.right.value(), self.top.value())
|
||||
return ('LayoutBox: %25s, (left: %1.3f) (bot: %1.3f) '
|
||||
'(right: %1.3f) (top: %1.3f) ') % args
|
||||
|
||||
|
||||
# Utility functions that act on layoutboxes...
|
||||
def hstack(boxes, padding=0, strength='strong'):
|
||||
'''
|
||||
Stack LayoutBox instances from left to right.
|
||||
`padding` is in figure-relative units.
|
||||
'''
|
||||
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].right + padding <= boxes[i].left)
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def hpack(boxes, padding=0, strength='strong'):
|
||||
'''
|
||||
Stack LayoutBox instances from left to right.
|
||||
'''
|
||||
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].right + padding == boxes[i].left)
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def vstack(boxes, padding=0, strength='strong'):
|
||||
'''
|
||||
Stack LayoutBox instances from top to bottom
|
||||
'''
|
||||
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].bottom - padding >= boxes[i].top)
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def vpack(boxes, padding=0, strength='strong'):
|
||||
'''
|
||||
Stack LayoutBox instances from top to bottom
|
||||
'''
|
||||
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].bottom - padding >= boxes[i].top)
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def match_heights(boxes, height_ratios=None, strength='medium'):
|
||||
'''
|
||||
Stack LayoutBox instances from top to bottom
|
||||
'''
|
||||
|
||||
if height_ratios is None:
|
||||
height_ratios = np.ones(len(boxes))
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].height ==
|
||||
boxes[i].height*height_ratios[i-1]/height_ratios[i])
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def match_widths(boxes, width_ratios=None, strength='medium'):
|
||||
'''
|
||||
Stack LayoutBox instances from top to bottom
|
||||
'''
|
||||
|
||||
if width_ratios is None:
|
||||
width_ratios = np.ones(len(boxes))
|
||||
for i in range(1, len(boxes)):
|
||||
c = (boxes[i-1].width ==
|
||||
boxes[i].width*width_ratios[i-1]/width_ratios[i])
|
||||
boxes[i].solver.addConstraint(c | strength)
|
||||
|
||||
|
||||
def vstackeq(boxes, padding=0, height_ratios=None):
|
||||
vstack(boxes, padding=padding)
|
||||
match_heights(boxes, height_ratios=height_ratios)
|
||||
|
||||
|
||||
def hstackeq(boxes, padding=0, width_ratios=None):
|
||||
hstack(boxes, padding=padding)
|
||||
match_widths(boxes, width_ratios=width_ratios)
|
||||
|
||||
|
||||
def align(boxes, attr, strength='strong'):
|
||||
cons = []
|
||||
for box in boxes[1:]:
|
||||
cons = (getattr(boxes[0], attr) == getattr(box, attr))
|
||||
boxes[0].solver.addConstraint(cons | strength)
|
||||
|
||||
|
||||
def match_top_margins(boxes, levels=1):
|
||||
box0 = boxes[0]
|
||||
top0 = box0
|
||||
for n in range(levels):
|
||||
top0 = top0.parent
|
||||
for box in boxes[1:]:
|
||||
topb = box
|
||||
for n in range(levels):
|
||||
topb = topb.parent
|
||||
c = (box0.top-top0.top == box.top-topb.top)
|
||||
box0.solver.addConstraint(c | 'strong')
|
||||
|
||||
|
||||
def match_bottom_margins(boxes, levels=1):
|
||||
box0 = boxes[0]
|
||||
top0 = box0
|
||||
for n in range(levels):
|
||||
top0 = top0.parent
|
||||
for box in boxes[1:]:
|
||||
topb = box
|
||||
for n in range(levels):
|
||||
topb = topb.parent
|
||||
c = (box0.bottom-top0.bottom == box.bottom-topb.bottom)
|
||||
box0.solver.addConstraint(c | 'strong')
|
||||
|
||||
|
||||
def match_left_margins(boxes, levels=1):
|
||||
box0 = boxes[0]
|
||||
top0 = box0
|
||||
for n in range(levels):
|
||||
top0 = top0.parent
|
||||
for box in boxes[1:]:
|
||||
topb = box
|
||||
for n in range(levels):
|
||||
topb = topb.parent
|
||||
c = (box0.left-top0.left == box.left-topb.left)
|
||||
box0.solver.addConstraint(c | 'strong')
|
||||
|
||||
|
||||
def match_right_margins(boxes, levels=1):
|
||||
box0 = boxes[0]
|
||||
top0 = box0
|
||||
for n in range(levels):
|
||||
top0 = top0.parent
|
||||
for box in boxes[1:]:
|
||||
topb = box
|
||||
for n in range(levels):
|
||||
topb = topb.parent
|
||||
c = (box0.right-top0.right == box.right-topb.right)
|
||||
box0.solver.addConstraint(c | 'strong')
|
||||
|
||||
|
||||
def match_width_margins(boxes, levels=1):
|
||||
match_left_margins(boxes, levels=levels)
|
||||
match_right_margins(boxes, levels=levels)
|
||||
|
||||
|
||||
def match_height_margins(boxes, levels=1):
|
||||
match_top_margins(boxes, levels=levels)
|
||||
match_bottom_margins(boxes, levels=levels)
|
||||
|
||||
|
||||
def match_margins(boxes, levels=1):
|
||||
match_width_margins(boxes, levels=levels)
|
||||
match_height_margins(boxes, levels=levels)
|
||||
|
||||
|
||||
_layoutboxobjnum = itertools.count()
|
||||
|
||||
|
||||
def seq_id():
|
||||
'''
|
||||
Generate a short sequential id for layoutbox objects...
|
||||
'''
|
||||
|
||||
global _layoutboxobjnum
|
||||
|
||||
return ('%06d' % (next(_layoutboxobjnum)))
|
||||
|
||||
|
||||
def print_children(lb):
|
||||
'''
|
||||
Print the children of the layoutbox
|
||||
'''
|
||||
print(lb)
|
||||
for child in lb.children:
|
||||
print_children(child)
|
||||
|
||||
|
||||
def nonetree(lb):
|
||||
'''
|
||||
Make all elements in this tree none... This signals not to do any more
|
||||
layout.
|
||||
'''
|
||||
if lb is not None:
|
||||
if lb.parent is None:
|
||||
# Clear the solver. Hopefully this garbage collects.
|
||||
lb.solver.reset()
|
||||
nonechildren(lb)
|
||||
else:
|
||||
nonetree(lb.parent)
|
||||
|
||||
|
||||
def nonechildren(lb):
|
||||
for child in lb.children:
|
||||
nonechildren(child)
|
||||
lb.artist._layoutbox = None
|
||||
lb = None
|
||||
|
||||
|
||||
def print_tree(lb):
|
||||
'''
|
||||
Print the tree of layoutboxes
|
||||
'''
|
||||
|
||||
if lb.parent is None:
|
||||
print('LayoutBox Tree\n')
|
||||
print('==============\n')
|
||||
print_children(lb)
|
||||
print('\n')
|
||||
else:
|
||||
print_tree(lb.parent)
|
||||
|
||||
|
||||
def plot_children(fig, box, level=0, printit=True):
|
||||
'''
|
||||
Simple plotting to show where boxes are
|
||||
'''
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
if isinstance(fig, matplotlib.figure.Figure):
|
||||
ax = fig.add_axes([0., 0., 1., 1.])
|
||||
ax.set_facecolor([1., 1., 1., 0.7])
|
||||
ax.set_alpha(0.3)
|
||||
fig.draw(fig.canvas.get_renderer())
|
||||
else:
|
||||
ax = fig
|
||||
|
||||
import matplotlib.patches as patches
|
||||
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
|
||||
if printit:
|
||||
print("Level:", level)
|
||||
for child in box.children:
|
||||
rect = child.get_rect()
|
||||
if printit:
|
||||
print(child)
|
||||
ax.add_patch(
|
||||
patches.Rectangle(
|
||||
(child.left.value(), child.bottom.value()), # (x,y)
|
||||
child.width.value(), # width
|
||||
child.height.value(), # height
|
||||
fc='none',
|
||||
alpha=0.8,
|
||||
ec=colors[level]
|
||||
)
|
||||
)
|
||||
if level > 0:
|
||||
name = child.name.split('.')[-1]
|
||||
if level % 2 == 0:
|
||||
ax.text(child.left.value(), child.bottom.value(), name,
|
||||
size=12-level, color=colors[level])
|
||||
else:
|
||||
ax.text(child.right.value(), child.top.value(), name,
|
||||
ha='right', va='top', size=12-level,
|
||||
color=colors[level])
|
||||
|
||||
plot_children(ax, child, level=level+1, printit=printit)
|
||||
@@ -1,134 +0,0 @@
|
||||
"""
|
||||
Manage figures for pyplot interface.
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import gc
|
||||
import sys
|
||||
|
||||
|
||||
class Gcf(object):
|
||||
"""
|
||||
Singleton to manage a set of integer-numbered figures.
|
||||
|
||||
This class is never instantiated; it consists of two class
|
||||
attributes (a list and a dictionary), and a set of static
|
||||
methods that operate on those attributes, accessing them
|
||||
directly as class attributes.
|
||||
|
||||
Attributes:
|
||||
|
||||
*figs*:
|
||||
dictionary of the form {*num*: *manager*, ...}
|
||||
|
||||
*_activeQue*:
|
||||
list of *managers*, with active one at the end
|
||||
|
||||
"""
|
||||
_activeQue = []
|
||||
figs = {}
|
||||
|
||||
@classmethod
|
||||
def get_fig_manager(cls, num):
|
||||
"""
|
||||
If figure manager *num* exists, make it the active
|
||||
figure and return the manager; otherwise return *None*.
|
||||
"""
|
||||
manager = cls.figs.get(num, None)
|
||||
if manager is not None:
|
||||
cls.set_active(manager)
|
||||
return manager
|
||||
|
||||
@classmethod
|
||||
def destroy(cls, num):
|
||||
"""
|
||||
Try to remove all traces of figure *num*.
|
||||
|
||||
In the interactive backends, this is bound to the
|
||||
window "destroy" and "delete" events.
|
||||
"""
|
||||
if not cls.has_fignum(num):
|
||||
return
|
||||
manager = cls.figs[num]
|
||||
manager.canvas.mpl_disconnect(manager._cidgcf)
|
||||
cls._activeQue.remove(manager)
|
||||
del cls.figs[num]
|
||||
manager.destroy()
|
||||
gc.collect(1)
|
||||
|
||||
@classmethod
|
||||
def destroy_fig(cls, fig):
|
||||
"*fig* is a Figure instance"
|
||||
num = next((manager.num for manager in cls.figs.values()
|
||||
if manager.canvas.figure == fig), None)
|
||||
if num is not None:
|
||||
cls.destroy(num)
|
||||
|
||||
@classmethod
|
||||
def destroy_all(cls):
|
||||
# this is need to ensure that gc is available in corner cases
|
||||
# where modules are being torn down after install with easy_install
|
||||
import gc # noqa
|
||||
for manager in list(cls.figs.values()):
|
||||
manager.canvas.mpl_disconnect(manager._cidgcf)
|
||||
manager.destroy()
|
||||
|
||||
cls._activeQue = []
|
||||
cls.figs.clear()
|
||||
gc.collect(1)
|
||||
|
||||
@classmethod
|
||||
def has_fignum(cls, num):
|
||||
"""
|
||||
Return *True* if figure *num* exists.
|
||||
"""
|
||||
return num in cls.figs
|
||||
|
||||
@classmethod
|
||||
def get_all_fig_managers(cls):
|
||||
"""
|
||||
Return a list of figure managers.
|
||||
"""
|
||||
return list(cls.figs.values())
|
||||
|
||||
@classmethod
|
||||
def get_num_fig_managers(cls):
|
||||
"""
|
||||
Return the number of figures being managed.
|
||||
"""
|
||||
return len(cls.figs)
|
||||
|
||||
@classmethod
|
||||
def get_active(cls):
|
||||
"""
|
||||
Return the manager of the active figure, or *None*.
|
||||
"""
|
||||
if len(cls._activeQue) == 0:
|
||||
return None
|
||||
else:
|
||||
return cls._activeQue[-1]
|
||||
|
||||
@classmethod
|
||||
def set_active(cls, manager):
|
||||
"""
|
||||
Make the figure corresponding to *manager* the active one.
|
||||
"""
|
||||
oldQue = cls._activeQue[:]
|
||||
cls._activeQue = []
|
||||
for m in oldQue:
|
||||
if m != manager:
|
||||
cls._activeQue.append(m)
|
||||
cls._activeQue.append(manager)
|
||||
cls.figs[manager.num] = manager
|
||||
|
||||
@classmethod
|
||||
def draw_all(cls, force=False):
|
||||
"""
|
||||
Redraw all figures registered with the pyplot
|
||||
state machine.
|
||||
"""
|
||||
for f_mgr in cls.get_all_fig_managers():
|
||||
if force or f_mgr.canvas.figure.stale:
|
||||
f_mgr.canvas.draw_idle()
|
||||
|
||||
atexit.register(Gcf.destroy_all)
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
# This file was generated by 'versioneer.py' (0.15) from
|
||||
# revision-control system data, or from the parent directory name of an
|
||||
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
||||
# of this file.
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
version_json = '''
|
||||
{
|
||||
"dirty": false,
|
||||
"error": null,
|
||||
"full-revisionid": "8858a0d1bdd149a0897789e8503ac586be14676d",
|
||||
"version": "3.0.2"
|
||||
}
|
||||
''' # END VERSION_JSON
|
||||
|
||||
|
||||
def get_versions():
|
||||
return json.loads(version_json)
|
||||
@@ -1,562 +0,0 @@
|
||||
"""
|
||||
This is a python interface to Adobe Font Metrics Files. Although a
|
||||
number of other python implementations exist, and may be more complete
|
||||
than this, it was decided not to go with them because they were
|
||||
either:
|
||||
|
||||
1) copyrighted or used a non-BSD compatible license
|
||||
|
||||
2) had too many dependencies and a free standing lib was needed
|
||||
|
||||
3) Did more than needed and it was easier to write afresh rather than
|
||||
figure out how to get just what was needed.
|
||||
|
||||
It is pretty easy to use, and requires only built-in python libs:
|
||||
|
||||
>>> from matplotlib import rcParams
|
||||
>>> import os.path
|
||||
>>> afm_fname = os.path.join(rcParams['datapath'],
|
||||
... 'fonts', 'afm', 'ptmr8a.afm')
|
||||
>>>
|
||||
>>> from matplotlib.afm import AFM
|
||||
>>> with open(afm_fname, 'rb') as fh:
|
||||
... afm = AFM(fh)
|
||||
>>> afm.string_width_height('What the heck?')
|
||||
(6220.0, 694)
|
||||
>>> afm.get_fontname()
|
||||
'Times-Roman'
|
||||
>>> afm.get_kern_dist('A', 'f')
|
||||
0
|
||||
>>> afm.get_kern_dist('A', 'y')
|
||||
-92.0
|
||||
>>> afm.get_bbox_char('!')
|
||||
[130, -9, 238, 676]
|
||||
|
||||
As in the Adobe Font Metrics File Format Specification, all dimensions
|
||||
are given in units of 1/1000 of the scale factor (point size) of the font
|
||||
being used.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ._mathtext_data import uni2type1
|
||||
from matplotlib.cbook import deprecated
|
||||
|
||||
|
||||
# some afm files have floats where we are expecting ints -- there is
|
||||
# probably a better way to handle this (support floats, round rather
|
||||
# than truncate). But I don't know what the best approach is now and
|
||||
# this change to _to_int should at least prevent mpl from crashing on
|
||||
# these JDH (2009-11-06)
|
||||
|
||||
def _to_int(x):
|
||||
return int(float(x))
|
||||
|
||||
|
||||
_to_float = float
|
||||
|
||||
|
||||
def _to_str(x):
|
||||
return x.decode('utf8')
|
||||
|
||||
|
||||
def _to_list_of_ints(s):
|
||||
s = s.replace(b',', b' ')
|
||||
return [_to_int(val) for val in s.split()]
|
||||
|
||||
|
||||
def _to_list_of_floats(s):
|
||||
return [_to_float(val) for val in s.split()]
|
||||
|
||||
|
||||
def _to_bool(s):
|
||||
if s.lower().strip() in (b'false', b'0', b'no'):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def _sanity_check(fh):
|
||||
"""
|
||||
Check if the file at least looks like AFM.
|
||||
If not, raise :exc:`RuntimeError`.
|
||||
"""
|
||||
|
||||
# Remember the file position in case the caller wants to
|
||||
# do something else with the file.
|
||||
pos = fh.tell()
|
||||
try:
|
||||
line = next(fh)
|
||||
finally:
|
||||
fh.seek(pos, 0)
|
||||
|
||||
# AFM spec, Section 4: The StartFontMetrics keyword [followed by a
|
||||
# version number] must be the first line in the file, and the
|
||||
# EndFontMetrics keyword must be the last non-empty line in the
|
||||
# file. We just check the first line.
|
||||
if not line.startswith(b'StartFontMetrics'):
|
||||
raise RuntimeError('Not an AFM file')
|
||||
|
||||
|
||||
def _parse_header(fh):
|
||||
"""
|
||||
Reads the font metrics header (up to the char metrics) and returns
|
||||
a dictionary mapping *key* to *val*. *val* will be converted to the
|
||||
appropriate python type as necessary; e.g.:
|
||||
|
||||
* 'False'->False
|
||||
* '0'->0
|
||||
* '-168 -218 1000 898'-> [-168, -218, 1000, 898]
|
||||
|
||||
Dictionary keys are
|
||||
|
||||
StartFontMetrics, FontName, FullName, FamilyName, Weight,
|
||||
ItalicAngle, IsFixedPitch, FontBBox, UnderlinePosition,
|
||||
UnderlineThickness, Version, Notice, EncodingScheme, CapHeight,
|
||||
XHeight, Ascender, Descender, StartCharMetrics
|
||||
|
||||
"""
|
||||
headerConverters = {
|
||||
b'StartFontMetrics': _to_float,
|
||||
b'FontName': _to_str,
|
||||
b'FullName': _to_str,
|
||||
b'FamilyName': _to_str,
|
||||
b'Weight': _to_str,
|
||||
b'ItalicAngle': _to_float,
|
||||
b'IsFixedPitch': _to_bool,
|
||||
b'FontBBox': _to_list_of_ints,
|
||||
b'UnderlinePosition': _to_int,
|
||||
b'UnderlineThickness': _to_int,
|
||||
b'Version': _to_str,
|
||||
b'Notice': _to_str,
|
||||
b'EncodingScheme': _to_str,
|
||||
b'CapHeight': _to_float, # Is the second version a mistake, or
|
||||
b'Capheight': _to_float, # do some AFM files contain 'Capheight'? -JKS
|
||||
b'XHeight': _to_float,
|
||||
b'Ascender': _to_float,
|
||||
b'Descender': _to_float,
|
||||
b'StdHW': _to_float,
|
||||
b'StdVW': _to_float,
|
||||
b'StartCharMetrics': _to_int,
|
||||
b'CharacterSet': _to_str,
|
||||
b'Characters': _to_int,
|
||||
}
|
||||
|
||||
d = {}
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if line.startswith(b'Comment'):
|
||||
continue
|
||||
lst = line.split(b' ', 1)
|
||||
|
||||
key = lst[0]
|
||||
if len(lst) == 2:
|
||||
val = lst[1]
|
||||
else:
|
||||
val = b''
|
||||
|
||||
try:
|
||||
d[key] = headerConverters[key](val)
|
||||
except ValueError:
|
||||
print('Value error parsing header in AFM:', key, val,
|
||||
file=sys.stderr)
|
||||
continue
|
||||
except KeyError:
|
||||
print('Found an unknown keyword in AFM header (was %r)' % key,
|
||||
file=sys.stderr)
|
||||
continue
|
||||
if key == b'StartCharMetrics':
|
||||
return d
|
||||
raise RuntimeError('Bad parse')
|
||||
|
||||
|
||||
CharMetrics = namedtuple('CharMetrics', 'width, name, bbox')
|
||||
CharMetrics.__doc__ = """
|
||||
Represents the character metrics of a single character.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The fields do currently only describe a subset of character metrics
|
||||
information defined in the AFM standard.
|
||||
"""
|
||||
CharMetrics.width.__doc__ = """The character width (WX)."""
|
||||
CharMetrics.name.__doc__ = """The character name (N)."""
|
||||
CharMetrics.bbox.__doc__ = """
|
||||
The bbox of the character (B) as a tuple (*llx*, *lly*, *urx*, *ury*)."""
|
||||
|
||||
|
||||
def _parse_char_metrics(fh):
|
||||
"""
|
||||
Parse the given filehandle for character metrics information and return
|
||||
the information as dicts.
|
||||
|
||||
It is assumed that the file cursor is on the line behind
|
||||
'StartCharMetrics'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ascii_d : dict
|
||||
A mapping "ASCII num of the character" to `.CharMetrics`.
|
||||
name_d : dict
|
||||
A mapping "character name" to `.CharMetrics`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function is incomplete per the standard, but thus far parses
|
||||
all the sample afm files tried.
|
||||
"""
|
||||
required_keys = {'C', 'WX', 'N', 'B'}
|
||||
|
||||
ascii_d = {}
|
||||
name_d = {}
|
||||
for line in fh:
|
||||
# We are defensively letting values be utf8. The spec requires
|
||||
# ascii, but there are non-compliant fonts in circulation
|
||||
line = _to_str(line.rstrip()) # Convert from byte-literal
|
||||
if line.startswith('EndCharMetrics'):
|
||||
return ascii_d, name_d
|
||||
# Split the metric line into a dictionary, keyed by metric identifiers
|
||||
vals = dict(s.strip().split(' ', 1) for s in line.split(';') if s)
|
||||
# There may be other metrics present, but only these are needed
|
||||
if not required_keys.issubset(vals):
|
||||
raise RuntimeError('Bad char metrics line: %s' % line)
|
||||
num = _to_int(vals['C'])
|
||||
wx = _to_float(vals['WX'])
|
||||
name = vals['N']
|
||||
bbox = _to_list_of_floats(vals['B'])
|
||||
bbox = list(map(int, bbox))
|
||||
metrics = CharMetrics(wx, name, bbox)
|
||||
# Workaround: If the character name is 'Euro', give it the
|
||||
# corresponding character code, according to WinAnsiEncoding (see PDF
|
||||
# Reference).
|
||||
if name == 'Euro':
|
||||
num = 128
|
||||
if num != -1:
|
||||
ascii_d[num] = metrics
|
||||
name_d[name] = metrics
|
||||
raise RuntimeError('Bad parse')
|
||||
|
||||
|
||||
def _parse_kern_pairs(fh):
|
||||
"""
|
||||
Return a kern pairs dictionary; keys are (*char1*, *char2*) tuples and
|
||||
values are the kern pair value. For example, a kern pairs line like
|
||||
``KPX A y -50``
|
||||
|
||||
will be represented as::
|
||||
|
||||
d[ ('A', 'y') ] = -50
|
||||
|
||||
"""
|
||||
|
||||
line = next(fh)
|
||||
if not line.startswith(b'StartKernPairs'):
|
||||
raise RuntimeError('Bad start of kern pairs data: %s' % line)
|
||||
|
||||
d = {}
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith(b'EndKernPairs'):
|
||||
next(fh) # EndKernData
|
||||
return d
|
||||
vals = line.split()
|
||||
if len(vals) != 4 or vals[0] != b'KPX':
|
||||
raise RuntimeError('Bad kern pairs line: %s' % line)
|
||||
c1, c2, val = _to_str(vals[1]), _to_str(vals[2]), _to_float(vals[3])
|
||||
d[(c1, c2)] = val
|
||||
raise RuntimeError('Bad kern pairs parse')
|
||||
|
||||
|
||||
CompositePart = namedtuple('CompositePart', 'name, dx, dy')
|
||||
CompositePart.__doc__ = """
|
||||
Represents the information on a composite element of a composite char."""
|
||||
CompositePart.name.__doc__ = """Name of the part, e.g. 'acute'."""
|
||||
CompositePart.dx.__doc__ = """x-displacement of the part from the origin."""
|
||||
CompositePart.dy.__doc__ = """y-displacement of the part from the origin."""
|
||||
|
||||
|
||||
def _parse_composites(fh):
|
||||
"""
|
||||
Parse the given filehandle for composites information return them as a
|
||||
dict.
|
||||
|
||||
It is assumed that the file cursor is on the line behind 'StartComposites'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
composites : dict
|
||||
A dict mapping composite character names to a parts list. The parts
|
||||
list is a list of `.CompositePart` entries describing the parts of
|
||||
the composite.
|
||||
|
||||
Example
|
||||
-------
|
||||
A composite definition line::
|
||||
|
||||
CC Aacute 2 ; PCC A 0 0 ; PCC acute 160 170 ;
|
||||
|
||||
will be represented as::
|
||||
|
||||
composites['Aacute'] = [CompositePart(name='A', dx=0, dy=0),
|
||||
CompositePart(name='acute', dx=160, dy=170)]
|
||||
|
||||
"""
|
||||
composites = {}
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith(b'EndComposites'):
|
||||
return composites
|
||||
vals = line.split(b';')
|
||||
cc = vals[0].split()
|
||||
name, numParts = cc[1], _to_int(cc[2])
|
||||
pccParts = []
|
||||
for s in vals[1:-1]:
|
||||
pcc = s.split()
|
||||
part = CompositePart(pcc[1], _to_float(pcc[2]), _to_float(pcc[3]))
|
||||
pccParts.append(part)
|
||||
composites[name] = pccParts
|
||||
|
||||
raise RuntimeError('Bad composites parse')
|
||||
|
||||
|
||||
def _parse_optional(fh):
|
||||
"""
|
||||
Parse the optional fields for kern pair data and composites.
|
||||
|
||||
Returns
|
||||
-------
|
||||
kern_data : dict
|
||||
A dict containing kerning information. May be empty.
|
||||
See `._parse_kern_pairs`.
|
||||
composites : dict
|
||||
A dict containing composite information. May be empty.
|
||||
See `._parse_composites`.
|
||||
"""
|
||||
optional = {
|
||||
b'StartKernData': _parse_kern_pairs,
|
||||
b'StartComposites': _parse_composites,
|
||||
}
|
||||
|
||||
d = {b'StartKernData': {},
|
||||
b'StartComposites': {}}
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
key = line.split()[0]
|
||||
|
||||
if key in optional:
|
||||
d[key] = optional[key](fh)
|
||||
|
||||
return d[b'StartKernData'], d[b'StartComposites']
|
||||
|
||||
|
||||
@deprecated("3.0", "Use the class AFM instead.")
|
||||
def parse_afm(fh):
|
||||
return _parse_afm(fh)
|
||||
|
||||
|
||||
def _parse_afm(fh):
|
||||
"""
|
||||
Parse the Adobe Font Metrics file in file handle *fh*.
|
||||
|
||||
Returns
|
||||
-------
|
||||
header : dict
|
||||
A header dict. See :func:`_parse_header`.
|
||||
cmetrics_by_ascii : dict
|
||||
From :func:`_parse_char_metrics`.
|
||||
cmetrics_by_name : dict
|
||||
From :func:`_parse_char_metrics`.
|
||||
kernpairs : dict
|
||||
From :func:`_parse_kern_pairs`.
|
||||
composites : dict
|
||||
From :func:`_parse_composites`
|
||||
|
||||
"""
|
||||
_sanity_check(fh)
|
||||
header = _parse_header(fh)
|
||||
cmetrics_by_ascii, cmetrics_by_name = _parse_char_metrics(fh)
|
||||
kernpairs, composites = _parse_optional(fh)
|
||||
return header, cmetrics_by_ascii, cmetrics_by_name, kernpairs, composites
|
||||
|
||||
|
||||
class AFM(object):
|
||||
|
||||
def __init__(self, fh):
|
||||
"""Parse the AFM file in file object *fh*."""
|
||||
(self._header,
|
||||
self._metrics,
|
||||
self._metrics_by_name,
|
||||
self._kern,
|
||||
self._composite) = _parse_afm(fh)
|
||||
|
||||
def get_bbox_char(self, c, isord=False):
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].bbox
|
||||
|
||||
def string_width_height(self, s):
|
||||
"""
|
||||
Return the string width (including kerning) and string height
|
||||
as a (*w*, *h*) tuple.
|
||||
"""
|
||||
if not len(s):
|
||||
return 0, 0
|
||||
total_width = 0
|
||||
namelast = None
|
||||
miny = 1e9
|
||||
maxy = 0
|
||||
for c in s:
|
||||
if c == '\n':
|
||||
continue
|
||||
wx, name, bbox = self._metrics[ord(c)]
|
||||
|
||||
total_width += wx + self._kern.get((namelast, name), 0)
|
||||
l, b, w, h = bbox
|
||||
miny = min(miny, b)
|
||||
maxy = max(maxy, b + h)
|
||||
|
||||
namelast = name
|
||||
|
||||
return total_width, maxy - miny
|
||||
|
||||
def get_str_bbox_and_descent(self, s):
|
||||
"""Return the string bounding box and the maximal descent."""
|
||||
if not len(s):
|
||||
return 0, 0, 0, 0, 0
|
||||
total_width = 0
|
||||
namelast = None
|
||||
miny = 1e9
|
||||
maxy = 0
|
||||
left = 0
|
||||
if not isinstance(s, str):
|
||||
s = _to_str(s)
|
||||
for c in s:
|
||||
if c == '\n':
|
||||
continue
|
||||
name = uni2type1.get(ord(c), 'question')
|
||||
try:
|
||||
wx, _, bbox = self._metrics_by_name[name]
|
||||
except KeyError:
|
||||
name = 'question'
|
||||
wx, _, bbox = self._metrics_by_name[name]
|
||||
total_width += wx + self._kern.get((namelast, name), 0)
|
||||
l, b, w, h = bbox
|
||||
left = min(left, l)
|
||||
miny = min(miny, b)
|
||||
maxy = max(maxy, b + h)
|
||||
|
||||
namelast = name
|
||||
|
||||
return left, miny, total_width, maxy - miny, -miny
|
||||
|
||||
def get_str_bbox(self, s):
|
||||
"""Return the string bounding box."""
|
||||
return self.get_str_bbox_and_descent(s)[:4]
|
||||
|
||||
def get_name_char(self, c, isord=False):
|
||||
"""Get the name of the character, i.e., ';' is 'semicolon'."""
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].name
|
||||
|
||||
def get_width_char(self, c, isord=False):
|
||||
"""
|
||||
Get the width of the character from the character metric WX field.
|
||||
"""
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].width
|
||||
|
||||
def get_width_from_char_name(self, name):
|
||||
"""Get the width of the character from a type1 character name."""
|
||||
return self._metrics_by_name[name].width
|
||||
|
||||
def get_height_char(self, c, isord=False):
|
||||
"""Get the bounding box (ink) height of character *c* (space is 0)."""
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].bbox[-1]
|
||||
|
||||
def get_kern_dist(self, c1, c2):
|
||||
"""
|
||||
Return the kerning pair distance (possibly 0) for chars *c1* and *c2*.
|
||||
"""
|
||||
name1, name2 = self.get_name_char(c1), self.get_name_char(c2)
|
||||
return self.get_kern_dist_from_name(name1, name2)
|
||||
|
||||
def get_kern_dist_from_name(self, name1, name2):
|
||||
"""
|
||||
Return the kerning pair distance (possibly 0) for chars
|
||||
*name1* and *name2*.
|
||||
"""
|
||||
return self._kern.get((name1, name2), 0)
|
||||
|
||||
def get_fontname(self):
|
||||
"""Return the font name, e.g., 'Times-Roman'."""
|
||||
return self._header[b'FontName']
|
||||
|
||||
def get_fullname(self):
|
||||
"""Return the font full name, e.g., 'Times-Roman'."""
|
||||
name = self._header.get(b'FullName')
|
||||
if name is None: # use FontName as a substitute
|
||||
name = self._header[b'FontName']
|
||||
return name
|
||||
|
||||
def get_familyname(self):
|
||||
"""Return the font family name, e.g., 'Times'."""
|
||||
name = self._header.get(b'FamilyName')
|
||||
if name is not None:
|
||||
return name
|
||||
|
||||
# FamilyName not specified so we'll make a guess
|
||||
name = self.get_fullname()
|
||||
extras = (r'(?i)([ -](regular|plain|italic|oblique|bold|semibold|'
|
||||
r'light|ultralight|extra|condensed))+$')
|
||||
return re.sub(extras, '', name)
|
||||
|
||||
@property
|
||||
def family_name(self):
|
||||
"""The font family name, e.g., 'Times'."""
|
||||
return self.get_familyname()
|
||||
|
||||
def get_weight(self):
|
||||
"""Return the font weight, e.g., 'Bold' or 'Roman'."""
|
||||
return self._header[b'Weight']
|
||||
|
||||
def get_angle(self):
|
||||
"""Return the fontangle as float."""
|
||||
return self._header[b'ItalicAngle']
|
||||
|
||||
def get_capheight(self):
|
||||
"""Return the cap height as float."""
|
||||
return self._header[b'CapHeight']
|
||||
|
||||
def get_xheight(self):
|
||||
"""Return the xheight as float."""
|
||||
return self._header[b'XHeight']
|
||||
|
||||
def get_underline_thickness(self):
|
||||
"""Return the underline thickness as float."""
|
||||
return self._header[b'UnderlineThickness']
|
||||
|
||||
def get_horizontal_stem_width(self):
|
||||
"""
|
||||
Return the standard horizontal stem width as float, or *None* if
|
||||
not specified in AFM file.
|
||||
"""
|
||||
return self._header.get(b'StdHW', None)
|
||||
|
||||
def get_vertical_stem_width(self):
|
||||
"""
|
||||
Return the standard vertical stem width as float, or *None* if
|
||||
not specified in AFM file.
|
||||
"""
|
||||
return self._header.get(b'StdVW', None)
|
||||
@@ -1,2 +0,0 @@
|
||||
from ._subplots import *
|
||||
from ._axes import *
|
||||
@@ -1,242 +0,0 @@
|
||||
import functools
|
||||
import warnings
|
||||
|
||||
from matplotlib import docstring
|
||||
import matplotlib.artist as martist
|
||||
from matplotlib.axes._axes import Axes
|
||||
from matplotlib.gridspec import GridSpec, SubplotSpec
|
||||
import matplotlib._layoutbox as layoutbox
|
||||
|
||||
|
||||
class SubplotBase(object):
|
||||
"""
|
||||
Base class for subplots, which are :class:`Axes` instances with
|
||||
additional methods to facilitate generating and manipulating a set
|
||||
of :class:`Axes` within a figure.
|
||||
"""
|
||||
|
||||
def __init__(self, fig, *args, **kwargs):
|
||||
"""
|
||||
*fig* is a :class:`matplotlib.figure.Figure` instance.
|
||||
|
||||
*args* is the tuple (*numRows*, *numCols*, *plotNum*), where
|
||||
the array of subplots in the figure has dimensions *numRows*,
|
||||
*numCols*, and where *plotNum* is the number of the subplot
|
||||
being created. *plotNum* starts at 1 in the upper left
|
||||
corner and increases to the right.
|
||||
|
||||
If *numRows* <= *numCols* <= *plotNum* < 10, *args* can be the
|
||||
decimal integer *numRows* * 100 + *numCols* * 10 + *plotNum*.
|
||||
"""
|
||||
|
||||
self.figure = fig
|
||||
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], SubplotSpec):
|
||||
self._subplotspec = args[0]
|
||||
else:
|
||||
try:
|
||||
s = str(int(args[0]))
|
||||
rows, cols, num = map(int, s)
|
||||
except ValueError:
|
||||
raise ValueError('Single argument to subplot must be '
|
||||
'a 3-digit integer')
|
||||
self._subplotspec = GridSpec(rows, cols,
|
||||
figure=self.figure)[num - 1]
|
||||
# num - 1 for converting from MATLAB to python indexing
|
||||
elif len(args) == 3:
|
||||
rows, cols, num = args
|
||||
rows = int(rows)
|
||||
cols = int(cols)
|
||||
if isinstance(num, tuple) and len(num) == 2:
|
||||
num = [int(n) for n in num]
|
||||
self._subplotspec = GridSpec(
|
||||
rows, cols,
|
||||
figure=self.figure)[(num[0] - 1):num[1]]
|
||||
else:
|
||||
if num < 1 or num > rows*cols:
|
||||
raise ValueError(
|
||||
("num must be 1 <= num <= {maxn}, not {num}"
|
||||
).format(maxn=rows*cols, num=num))
|
||||
self._subplotspec = GridSpec(
|
||||
rows, cols, figure=self.figure)[int(num) - 1]
|
||||
# num - 1 for converting from MATLAB to python indexing
|
||||
else:
|
||||
raise ValueError('Illegal argument(s) to subplot: %s' % (args,))
|
||||
|
||||
self.update_params()
|
||||
|
||||
# _axes_class is set in the subplot_class_factory
|
||||
self._axes_class.__init__(self, fig, self.figbox, **kwargs)
|
||||
# add a layout box to this, for both the full axis, and the poss
|
||||
# of the axis. We need both because the axes may become smaller
|
||||
# due to parasitic axes and hence no longer fill the subplotspec.
|
||||
if self._subplotspec._layoutbox is None:
|
||||
self._layoutbox = None
|
||||
self._poslayoutbox = None
|
||||
else:
|
||||
name = self._subplotspec._layoutbox.name + '.ax'
|
||||
name = name + layoutbox.seq_id()
|
||||
self._layoutbox = layoutbox.LayoutBox(
|
||||
parent=self._subplotspec._layoutbox,
|
||||
name=name,
|
||||
artist=self)
|
||||
self._poslayoutbox = layoutbox.LayoutBox(
|
||||
parent=self._layoutbox,
|
||||
name=self._layoutbox.name+'.pos',
|
||||
pos=True, subplot=True, artist=self)
|
||||
|
||||
def __reduce__(self):
|
||||
# get the first axes class which does not inherit from a subplotbase
|
||||
axes_class = next(
|
||||
c for c in type(self).__mro__
|
||||
if issubclass(c, Axes) and not issubclass(c, SubplotBase))
|
||||
return (_picklable_subplot_class_constructor,
|
||||
(axes_class,),
|
||||
self.__getstate__())
|
||||
|
||||
def get_geometry(self):
|
||||
"""get the subplot geometry, e.g., 2,2,3"""
|
||||
rows, cols, num1, num2 = self.get_subplotspec().get_geometry()
|
||||
return rows, cols, num1 + 1 # for compatibility
|
||||
|
||||
# COVERAGE NOTE: Never used internally or from examples
|
||||
def change_geometry(self, numrows, numcols, num):
|
||||
"""change subplot geometry, e.g., from 1,1,1 to 2,2,3"""
|
||||
self._subplotspec = GridSpec(numrows, numcols,
|
||||
figure=self.figure)[num - 1]
|
||||
self.update_params()
|
||||
self.set_position(self.figbox)
|
||||
|
||||
def get_subplotspec(self):
|
||||
"""get the SubplotSpec instance associated with the subplot"""
|
||||
return self._subplotspec
|
||||
|
||||
def set_subplotspec(self, subplotspec):
|
||||
"""set the SubplotSpec instance associated with the subplot"""
|
||||
self._subplotspec = subplotspec
|
||||
|
||||
def get_gridspec(self):
|
||||
"""get the GridSpec instance associated with the subplot"""
|
||||
return self._subplotspec.get_gridspec()
|
||||
|
||||
def update_params(self):
|
||||
"""update the subplot position from fig.subplotpars"""
|
||||
|
||||
self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols = \
|
||||
self.get_subplotspec().get_position(self.figure,
|
||||
return_all=True)
|
||||
|
||||
def is_first_col(self):
|
||||
return self.colNum == 0
|
||||
|
||||
def is_first_row(self):
|
||||
return self.rowNum == 0
|
||||
|
||||
def is_last_row(self):
|
||||
return self.rowNum == self.numRows - 1
|
||||
|
||||
def is_last_col(self):
|
||||
return self.colNum == self.numCols - 1
|
||||
|
||||
# COVERAGE NOTE: Never used internally.
|
||||
def label_outer(self):
|
||||
"""Only show "outer" labels and tick labels.
|
||||
|
||||
x-labels are only kept for subplots on the last row; y-labels only for
|
||||
subplots on the first column.
|
||||
"""
|
||||
lastrow = self.is_last_row()
|
||||
firstcol = self.is_first_col()
|
||||
if not lastrow:
|
||||
for label in self.get_xticklabels(which="both"):
|
||||
label.set_visible(False)
|
||||
self.get_xaxis().get_offset_text().set_visible(False)
|
||||
self.set_xlabel("")
|
||||
if not firstcol:
|
||||
for label in self.get_yticklabels(which="both"):
|
||||
label.set_visible(False)
|
||||
self.get_yaxis().get_offset_text().set_visible(False)
|
||||
self.set_ylabel("")
|
||||
|
||||
def _make_twin_axes(self, *kl, **kwargs):
|
||||
"""
|
||||
Make a twinx axes of self. This is used for twinx and twiny.
|
||||
"""
|
||||
from matplotlib.projections import process_projection_requirements
|
||||
if 'sharex' in kwargs and 'sharey' in kwargs:
|
||||
# The following line is added in v2.2 to avoid breaking Seaborn,
|
||||
# which currently uses this internal API.
|
||||
if kwargs["sharex"] is not self and kwargs["sharey"] is not self:
|
||||
raise ValueError("Twinned Axes may share only one axis.")
|
||||
kl = (self.get_subplotspec(),) + kl
|
||||
projection_class, kwargs, key = process_projection_requirements(
|
||||
self.figure, *kl, **kwargs)
|
||||
|
||||
ax2 = subplot_class_factory(projection_class)(self.figure,
|
||||
*kl, **kwargs)
|
||||
self.figure.add_subplot(ax2)
|
||||
self.set_adjustable('datalim')
|
||||
ax2.set_adjustable('datalim')
|
||||
|
||||
if self._layoutbox is not None and ax2._layoutbox is not None:
|
||||
# make the layout boxes be explicitly the same
|
||||
ax2._layoutbox.constrain_same(self._layoutbox)
|
||||
ax2._poslayoutbox.constrain_same(self._poslayoutbox)
|
||||
|
||||
self._twinned_axes.join(self, ax2)
|
||||
return ax2
|
||||
|
||||
|
||||
# this here to support cartopy which was using a private part of the
|
||||
# API to register their Axes subclasses.
|
||||
|
||||
# In 3.1 this should be changed to a dict subclass that warns on use
|
||||
# In 3.3 to a dict subclass that raises a useful exception on use
|
||||
# In 3.4 should be removed
|
||||
|
||||
# The slow timeline is to give cartopy enough time to get several
|
||||
# release out before we break them.
|
||||
_subplot_classes = {}
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def subplot_class_factory(axes_class=None):
|
||||
"""
|
||||
This makes a new class that inherits from `.SubplotBase` and the
|
||||
given axes_class (which is assumed to be a subclass of `.axes.Axes`).
|
||||
This is perhaps a little bit roundabout to make a new class on
|
||||
the fly like this, but it means that a new Subplot class does
|
||||
not have to be created for every type of Axes.
|
||||
"""
|
||||
if axes_class is None:
|
||||
axes_class = Axes
|
||||
try:
|
||||
# Avoid creating two different instances of GeoAxesSubplot...
|
||||
# Only a temporary backcompat fix. This should be removed in
|
||||
# 3.4
|
||||
return next(cls for cls in SubplotBase.__subclasses__()
|
||||
if cls.__bases__ == (SubplotBase, axes_class))
|
||||
except StopIteration:
|
||||
return type("%sSubplot" % axes_class.__name__,
|
||||
(SubplotBase, axes_class),
|
||||
{'_axes_class': axes_class})
|
||||
|
||||
|
||||
# This is provided for backward compatibility
|
||||
Subplot = subplot_class_factory()
|
||||
|
||||
|
||||
def _picklable_subplot_class_constructor(axes_class):
|
||||
"""
|
||||
This stub class exists to return the appropriate subplot class when called
|
||||
with an axes class. This is purely to allow pickling of Axes and Subplots.
|
||||
"""
|
||||
subplot_class = subplot_class_factory(axes_class)
|
||||
return subplot_class.__new__(subplot_class)
|
||||
|
||||
|
||||
docstring.interpd.update(Axes=martist.kwdoc(Axes))
|
||||
docstring.dedent_interpd(Axes.__init__)
|
||||
|
||||
docstring.interpd.update(Subplot=martist.kwdoc(Axes))
|
||||
@@ -1,433 +0,0 @@
|
||||
"""
|
||||
`ToolManager`
|
||||
Class that makes the bridge between user interaction (key press,
|
||||
toolbar clicks, ..) and the actions in response to the user inputs.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
import matplotlib.cbook as cbook
|
||||
import matplotlib.widgets as widgets
|
||||
from matplotlib.rcsetup import validate_stringlist
|
||||
import matplotlib.backend_tools as tools
|
||||
|
||||
|
||||
class ToolEvent(object):
|
||||
"""Event for tool manipulation (add/remove)"""
|
||||
def __init__(self, name, sender, tool, data=None):
|
||||
self.name = name
|
||||
self.sender = sender
|
||||
self.tool = tool
|
||||
self.data = data
|
||||
|
||||
|
||||
class ToolTriggerEvent(ToolEvent):
|
||||
"""Event to inform that a tool has been triggered"""
|
||||
def __init__(self, name, sender, tool, canvasevent=None, data=None):
|
||||
ToolEvent.__init__(self, name, sender, tool, data)
|
||||
self.canvasevent = canvasevent
|
||||
|
||||
|
||||
class ToolManagerMessageEvent(object):
|
||||
"""
|
||||
Event carrying messages from toolmanager
|
||||
|
||||
Messages usually get displayed to the user by the toolbar
|
||||
"""
|
||||
def __init__(self, name, sender, message):
|
||||
self.name = name
|
||||
self.sender = sender
|
||||
self.message = message
|
||||
|
||||
|
||||
class ToolManager(object):
|
||||
"""
|
||||
Helper class that groups all the user interactions for a Figure.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
figure: `Figure`
|
||||
keypresslock: `widgets.LockDraw`
|
||||
`LockDraw` object to know if the `canvas` key_press_event is locked
|
||||
messagelock: `widgets.LockDraw`
|
||||
`LockDraw` object to know if the message is available to write
|
||||
"""
|
||||
|
||||
def __init__(self, figure=None):
|
||||
warnings.warn('Treat the new Tool classes introduced in v1.5 as ' +
|
||||
'experimental for now, the API will likely change in ' +
|
||||
'version 2.1 and perhaps the rcParam as well')
|
||||
|
||||
self._key_press_handler_id = None
|
||||
|
||||
self._tools = {}
|
||||
self._keys = {}
|
||||
self._toggled = {}
|
||||
self._callbacks = cbook.CallbackRegistry()
|
||||
|
||||
# to process keypress event
|
||||
self.keypresslock = widgets.LockDraw()
|
||||
self.messagelock = widgets.LockDraw()
|
||||
|
||||
self._figure = None
|
||||
self.set_figure(figure)
|
||||
|
||||
@property
|
||||
def canvas(self):
|
||||
"""Canvas managed by FigureManager"""
|
||||
if not self._figure:
|
||||
return None
|
||||
return self._figure.canvas
|
||||
|
||||
@property
|
||||
def figure(self):
|
||||
"""Figure that holds the canvas"""
|
||||
return self._figure
|
||||
|
||||
@figure.setter
|
||||
def figure(self, figure):
|
||||
self.set_figure(figure)
|
||||
|
||||
def set_figure(self, figure, update_tools=True):
|
||||
"""
|
||||
Bind the given figure to the tools.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
figure : `.Figure`
|
||||
update_tools : bool
|
||||
Force tools to update figure
|
||||
"""
|
||||
if self._key_press_handler_id:
|
||||
self.canvas.mpl_disconnect(self._key_press_handler_id)
|
||||
self._figure = figure
|
||||
if figure:
|
||||
self._key_press_handler_id = self.canvas.mpl_connect(
|
||||
'key_press_event', self._key_press)
|
||||
if update_tools:
|
||||
for tool in self._tools.values():
|
||||
tool.figure = figure
|
||||
|
||||
def toolmanager_connect(self, s, func):
|
||||
"""
|
||||
Connect event with string *s* to *func*.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
s : String
|
||||
Name of the event
|
||||
|
||||
The following events are recognized
|
||||
|
||||
- 'tool_message_event'
|
||||
- 'tool_removed_event'
|
||||
- 'tool_added_event'
|
||||
|
||||
For every tool added a new event is created
|
||||
|
||||
- 'tool_trigger_TOOLNAME`
|
||||
Where TOOLNAME is the id of the tool.
|
||||
|
||||
func : function
|
||||
Function to be called with signature
|
||||
def func(event)
|
||||
"""
|
||||
return self._callbacks.connect(s, func)
|
||||
|
||||
def toolmanager_disconnect(self, cid):
|
||||
"""
|
||||
Disconnect callback id *cid*
|
||||
|
||||
Example usage::
|
||||
|
||||
cid = toolmanager.toolmanager_connect('tool_trigger_zoom',
|
||||
on_press)
|
||||
#...later
|
||||
toolmanager.toolmanager_disconnect(cid)
|
||||
"""
|
||||
return self._callbacks.disconnect(cid)
|
||||
|
||||
def message_event(self, message, sender=None):
|
||||
""" Emit a `ToolManagerMessageEvent`"""
|
||||
if sender is None:
|
||||
sender = self
|
||||
|
||||
s = 'tool_message_event'
|
||||
event = ToolManagerMessageEvent(s, sender, message)
|
||||
self._callbacks.process(s, event)
|
||||
|
||||
@property
|
||||
def active_toggle(self):
|
||||
"""Currently toggled tools"""
|
||||
|
||||
return self._toggled
|
||||
|
||||
def get_tool_keymap(self, name):
|
||||
"""
|
||||
Get the keymap associated with the specified tool
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : string
|
||||
Name of the Tool
|
||||
|
||||
Returns
|
||||
-------
|
||||
list : list of keys associated with the Tool
|
||||
"""
|
||||
|
||||
keys = [k for k, i in self._keys.items() if i == name]
|
||||
return keys
|
||||
|
||||
def _remove_keys(self, name):
|
||||
for k in self.get_tool_keymap(name):
|
||||
del self._keys[k]
|
||||
|
||||
def update_keymap(self, name, *keys):
|
||||
"""
|
||||
Set the keymap to associate with the specified tool
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : string
|
||||
Name of the Tool
|
||||
keys : keys to associate with the Tool
|
||||
"""
|
||||
|
||||
if name not in self._tools:
|
||||
raise KeyError('%s not in Tools' % name)
|
||||
|
||||
self._remove_keys(name)
|
||||
|
||||
for key in keys:
|
||||
for k in validate_stringlist(key):
|
||||
if k in self._keys:
|
||||
warnings.warn('Key %s changed from %s to %s' %
|
||||
(k, self._keys[k], name))
|
||||
self._keys[k] = name
|
||||
|
||||
def remove_tool(self, name):
|
||||
"""
|
||||
Remove tool from `ToolManager`
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : string
|
||||
Name of the Tool
|
||||
"""
|
||||
|
||||
tool = self.get_tool(name)
|
||||
tool.destroy()
|
||||
|
||||
# If is a toggle tool and toggled, untoggle
|
||||
if getattr(tool, 'toggled', False):
|
||||
self.trigger_tool(tool, 'toolmanager')
|
||||
|
||||
self._remove_keys(name)
|
||||
|
||||
s = 'tool_removed_event'
|
||||
event = ToolEvent(s, self, tool)
|
||||
self._callbacks.process(s, event)
|
||||
|
||||
del self._tools[name]
|
||||
|
||||
def add_tool(self, name, tool, *args, **kwargs):
|
||||
"""
|
||||
Add *tool* to `ToolManager`
|
||||
|
||||
If successful adds a new event `tool_trigger_name` where **name** is
|
||||
the **name** of the tool, this event is fired everytime
|
||||
the tool is triggered.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Name of the tool, treated as the ID, has to be unique
|
||||
tool : class_like, i.e. str or type
|
||||
Reference to find the class of the Tool to added.
|
||||
|
||||
Notes
|
||||
-----
|
||||
args and kwargs get passed directly to the tools constructor.
|
||||
|
||||
See Also
|
||||
--------
|
||||
matplotlib.backend_tools.ToolBase : The base class for tools.
|
||||
"""
|
||||
|
||||
tool_cls = self._get_cls_to_instantiate(tool)
|
||||
if not tool_cls:
|
||||
raise ValueError('Impossible to find class for %s' % str(tool))
|
||||
|
||||
if name in self._tools:
|
||||
warnings.warn('A "Tool class" with the same name already exists, '
|
||||
'not added')
|
||||
return self._tools[name]
|
||||
|
||||
tool_obj = tool_cls(self, name, *args, **kwargs)
|
||||
self._tools[name] = tool_obj
|
||||
|
||||
if tool_cls.default_keymap is not None:
|
||||
self.update_keymap(name, tool_cls.default_keymap)
|
||||
|
||||
# For toggle tools init the radio_group in self._toggled
|
||||
if isinstance(tool_obj, tools.ToolToggleBase):
|
||||
# None group is not mutually exclusive, a set is used to keep track
|
||||
# of all toggled tools in this group
|
||||
if tool_obj.radio_group is None:
|
||||
self._toggled.setdefault(None, set())
|
||||
else:
|
||||
self._toggled.setdefault(tool_obj.radio_group, None)
|
||||
|
||||
# If initially toggled
|
||||
if tool_obj.toggled:
|
||||
self._handle_toggle(tool_obj, None, None, None)
|
||||
tool_obj.set_figure(self.figure)
|
||||
|
||||
self._tool_added_event(tool_obj)
|
||||
return tool_obj
|
||||
|
||||
def _tool_added_event(self, tool):
|
||||
s = 'tool_added_event'
|
||||
event = ToolEvent(s, self, tool)
|
||||
self._callbacks.process(s, event)
|
||||
|
||||
def _handle_toggle(self, tool, sender, canvasevent, data):
|
||||
"""
|
||||
Toggle tools, need to untoggle prior to using other Toggle tool
|
||||
Called from trigger_tool
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tool: Tool object
|
||||
sender: object
|
||||
Object that wishes to trigger the tool
|
||||
canvasevent : Event
|
||||
Original Canvas event or None
|
||||
data : Object
|
||||
Extra data to pass to the tool when triggering
|
||||
"""
|
||||
|
||||
radio_group = tool.radio_group
|
||||
# radio_group None is not mutually exclusive
|
||||
# just keep track of toggled tools in this group
|
||||
if radio_group is None:
|
||||
if tool.name in self._toggled[None]:
|
||||
self._toggled[None].remove(tool.name)
|
||||
else:
|
||||
self._toggled[None].add(tool.name)
|
||||
return
|
||||
|
||||
# If the tool already has a toggled state, untoggle it
|
||||
if self._toggled[radio_group] == tool.name:
|
||||
toggled = None
|
||||
# If no tool was toggled in the radio_group
|
||||
# toggle it
|
||||
elif self._toggled[radio_group] is None:
|
||||
toggled = tool.name
|
||||
# Other tool in the radio_group is toggled
|
||||
else:
|
||||
# Untoggle previously toggled tool
|
||||
self.trigger_tool(self._toggled[radio_group],
|
||||
self,
|
||||
canvasevent,
|
||||
data)
|
||||
toggled = tool.name
|
||||
|
||||
# Keep track of the toggled tool in the radio_group
|
||||
self._toggled[radio_group] = toggled
|
||||
|
||||
def _get_cls_to_instantiate(self, callback_class):
|
||||
# Find the class that corresponds to the tool
|
||||
if isinstance(callback_class, str):
|
||||
# FIXME: make more complete searching structure
|
||||
if callback_class in globals():
|
||||
callback_class = globals()[callback_class]
|
||||
else:
|
||||
mod = 'backend_tools'
|
||||
current_module = __import__(mod,
|
||||
globals(), locals(), [mod], 1)
|
||||
|
||||
callback_class = getattr(current_module, callback_class, False)
|
||||
if callable(callback_class):
|
||||
return callback_class
|
||||
else:
|
||||
return None
|
||||
|
||||
def trigger_tool(self, name, sender=None, canvasevent=None,
|
||||
data=None):
|
||||
"""
|
||||
Trigger a tool and emit the tool_trigger_[name] event
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : string
|
||||
Name of the tool
|
||||
sender: object
|
||||
Object that wishes to trigger the tool
|
||||
canvasevent : Event
|
||||
Original Canvas event or None
|
||||
data : Object
|
||||
Extra data to pass to the tool when triggering
|
||||
"""
|
||||
tool = self.get_tool(name)
|
||||
if tool is None:
|
||||
return
|
||||
|
||||
if sender is None:
|
||||
sender = self
|
||||
|
||||
self._trigger_tool(name, sender, canvasevent, data)
|
||||
|
||||
s = 'tool_trigger_%s' % name
|
||||
event = ToolTriggerEvent(s, sender, tool, canvasevent, data)
|
||||
self._callbacks.process(s, event)
|
||||
|
||||
def _trigger_tool(self, name, sender=None, canvasevent=None, data=None):
|
||||
"""
|
||||
Trigger on a tool
|
||||
|
||||
Method to actually trigger the tool
|
||||
"""
|
||||
tool = self.get_tool(name)
|
||||
|
||||
if isinstance(tool, tools.ToolToggleBase):
|
||||
self._handle_toggle(tool, sender, canvasevent, data)
|
||||
|
||||
# Important!!!
|
||||
# This is where the Tool object gets triggered
|
||||
tool.trigger(sender, canvasevent, data)
|
||||
|
||||
def _key_press(self, event):
|
||||
if event.key is None or self.keypresslock.locked():
|
||||
return
|
||||
|
||||
name = self._keys.get(event.key, None)
|
||||
if name is None:
|
||||
return
|
||||
self.trigger_tool(name, canvasevent=event)
|
||||
|
||||
@property
|
||||
def tools(self):
|
||||
"""Return the tools controlled by `ToolManager`"""
|
||||
|
||||
return self._tools
|
||||
|
||||
def get_tool(self, name, warn=True):
|
||||
"""
|
||||
Return the tool object, also accepts the actual tool for convenience
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str, ToolBase
|
||||
Name of the tool, or the tool itself
|
||||
warn : bool, optional
|
||||
If this method should give warnings.
|
||||
"""
|
||||
if isinstance(name, tools.ToolBase) and name.name in self._tools:
|
||||
return name
|
||||
if name not in self._tools:
|
||||
if warn:
|
||||
warnings.warn("ToolManager does not control tool %s" % name)
|
||||
return None
|
||||
return self._tools[name]
|
||||
@@ -1,107 +0,0 @@
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import matplotlib
|
||||
from matplotlib import cbook
|
||||
from matplotlib.backend_bases import _Backend
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# NOTE: plt.switch_backend() (called at import time) will add a "backend"
|
||||
# attribute here for backcompat.
|
||||
|
||||
|
||||
def _get_running_interactive_framework():
|
||||
"""
|
||||
Return the interactive framework whose event loop is currently running, if
|
||||
any, or "headless" if no event loop can be started, or None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[str]
|
||||
One of the following values: "qt5", "qt4", "gtk3", "wx", "tk",
|
||||
"macosx", "headless", ``None``.
|
||||
"""
|
||||
QtWidgets = (sys.modules.get("PyQt5.QtWidgets")
|
||||
or sys.modules.get("PySide2.QtWidgets"))
|
||||
if QtWidgets and QtWidgets.QApplication.instance():
|
||||
return "qt5"
|
||||
QtGui = (sys.modules.get("PyQt4.QtGui")
|
||||
or sys.modules.get("PySide.QtGui"))
|
||||
if QtGui and QtGui.QApplication.instance():
|
||||
return "qt4"
|
||||
Gtk = (sys.modules.get("gi.repository.Gtk")
|
||||
or sys.modules.get("pgi.repository.Gtk"))
|
||||
if Gtk and Gtk.main_level():
|
||||
return "gtk3"
|
||||
wx = sys.modules.get("wx")
|
||||
if wx and wx.GetApp():
|
||||
return "wx"
|
||||
tkinter = sys.modules.get("tkinter")
|
||||
if tkinter:
|
||||
for frame in sys._current_frames().values():
|
||||
while frame:
|
||||
if frame.f_code == tkinter.mainloop.__code__:
|
||||
return "tk"
|
||||
frame = frame.f_back
|
||||
if 'matplotlib.backends._macosx' in sys.modules:
|
||||
if sys.modules["matplotlib.backends._macosx"].event_loop_is_running():
|
||||
return "macosx"
|
||||
if sys.platform.startswith("linux") and not os.environ.get("DISPLAY"):
|
||||
return "headless"
|
||||
return None
|
||||
|
||||
|
||||
@cbook.deprecated("3.0")
|
||||
def pylab_setup(name=None):
|
||||
"""
|
||||
Return new_figure_manager, draw_if_interactive and show for pyplot.
|
||||
|
||||
This provides the backend-specific functions that are used by pyplot to
|
||||
abstract away the difference between backends.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str, optional
|
||||
The name of the backend to use. If `None`, falls back to
|
||||
``matplotlib.get_backend()`` (which return :rc:`backend`).
|
||||
|
||||
Returns
|
||||
-------
|
||||
backend_mod : module
|
||||
The module which contains the backend of choice
|
||||
|
||||
new_figure_manager : function
|
||||
Create a new figure manager (roughly maps to GUI window)
|
||||
|
||||
draw_if_interactive : function
|
||||
Redraw the current figure if pyplot is interactive
|
||||
|
||||
show : function
|
||||
Show (and possibly block) any unshown figures.
|
||||
"""
|
||||
# Import the requested backend into a generic module object.
|
||||
if name is None:
|
||||
name = matplotlib.get_backend()
|
||||
backend_name = (name[9:] if name.startswith("module://")
|
||||
else "matplotlib.backends.backend_{}".format(name.lower()))
|
||||
backend_mod = importlib.import_module(backend_name)
|
||||
# Create a local Backend class whose body corresponds to the contents of
|
||||
# the backend module. This allows the Backend class to fill in the missing
|
||||
# methods through inheritance.
|
||||
Backend = type("Backend", (_Backend,), vars(backend_mod))
|
||||
|
||||
# Need to keep a global reference to the backend for compatibility reasons.
|
||||
# See https://github.com/matplotlib/matplotlib/issues/6092
|
||||
global backend
|
||||
backend = name
|
||||
|
||||
_log.debug('backend %s version %s', name, Backend.backend_version)
|
||||
return (backend_mod,
|
||||
Backend.new_figure_manager,
|
||||
Backend.draw_if_interactive,
|
||||
Backend.show)
|
||||