diff --git a/.travis.yml b/.travis.yml index 47931f7680ff..1f1d19aa03a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,12 @@ sudo: false +cache: + directories: + - $HOME/.ccache + - $HOME/.cache/pip + - $HOME/.cache/matplotlib + - $HOME/Library/Caches/pip + addons: apt: packages: @@ -8,11 +15,16 @@ addons: - gdb - mencoder - dvipng + - pgf + - lmodern + - cm-super - texlive-latex-base - texlive-latex-extra - texlive-fonts-recommended - texlive-latex-recommended + - texlive-xetex - graphviz + - libgeos-dev # - fonts-humor-sans # sources: # - debian-sid @@ -26,9 +38,12 @@ env: - secure: "dfjNqGKzQG5bu3FnDNwLG8H/C4QoieFo4PfFmZPdM2RY7WIzukwKFNT6kiDfOrpwt+2bR7FhzjOGlDECGtlGOtYPN8XuXGjhcP4a4IfakdbDfF+D3NPIpf5VlE6776k0VpvcZBTMYJKNFIMc7QPkOwjvNJ2aXyfe3hBuGlKJzQU=" - BUILD_DOCS=false - NUMPY=numpy + - OPENBLAS_NUM_THREADS=1 - PANDAS= - NPROC=2 - TEST_ARGS=--no-pep8 + - NOSE_ARGS="--processes=$NPROC --process-timeout=300" + - PYTHON_ARGS= language: python @@ -37,50 +52,60 @@ matrix: - python: 2.7 env: MOCK=mock NUMPY=numpy==1.6 - python: 3.4 + env: PYTHON_ARGS=-OO - python: 3.5 - env: PANDAS=pandas + env: PANDAS=pandas NOSE_ARGS=--with-coverage - python: 3.5 env: TEST_ARGS=--pep8 - - python: 2.7 - env: BUILD_DOCS=true MOCK=mock + - python: 3.5 + env: BUILD_DOCS=true - python: "nightly" env: PRE=--pre + - os: osx + osx_image: xcode7.3 + language: generic + env: MOCK=mock NOSE_ARGS= allow_failures: - python: "nightly" before_install: - - source tools/travis_tools.sh - # Install into our own pristine virtualenv - - virtualenv --python=python venv - - source venv/bin/activate + - | + # Install into our own pristine virtualenv + if [[ $TRAVIS_OS_NAME != 'osx' ]]; then + virtualenv --python=python venv + source venv/bin/activate + export PATH=/usr/lib/ccache:$PATH + else + brew update + brew tap homebrew/gui + brew install python libpng ffmpeg imagemagick mplayer ccache + # We could install ghostscript and inkscape here to test svg and pdf + # but this makes the test time really long. + # brew install ghostscript inkscape + export PATH=/usr/local/opt/ccache/libexec:$PATH + fi install: - # Upgrade pip and setuptools. Mock has issues with the default version of - # setuptools - | + # Setup environment + ccache -s + # Upgrade pip and setuptools and wheel to get as clean an install as possible pip install --upgrade pip + pip install --upgrade wheel pip install --upgrade setuptools - # Install only from travis wheelhouse - - if [ -z "$PRE" ]; then - wheelhouse_pip_install python-dateutil $NUMPY $PANDAS pyparsing!=2.0.4 pillow sphinx!=1.3.0 $MOCK; - else - pip install $PRE python-dateutil $NUMPY pyparsing!=2.0.4 pillow sphinx!=1.3.0; - fi - # Always install from pypi - - pip install $PRE pep8 cycler - - 'pip install https://github.com/tacaswell/nose/zipball/mnt_py36_compat#egg=nose' - - # We manually install humor sans using the package from Ubuntu 14.10. Unfortunatly humor sans is not - # availible in the Ubuntu version used by Travis but we can manually install the deb from a later - # version since is it basically just a .ttf file - # The current Travis Ubuntu image is to old to search .local/share/fonts so we store fonts in .fonts - - # We install ipython to use the console highlighting. From IPython 3 this depends on jsonschema and mistune. - # Neihter is installed as a dependency of IPython since they are not used by the IPython console. - | + # Install dependencies from pypi + pip install $PRE python-dateutil $NUMPY pyparsing!=2.1.2 $PANDAS pep8 cycler coveralls coverage + pip install $PRE pillow sphinx!=1.3.0 $MOCK numpydoc ipython colorspacious + # Install nose from a build which has partial + # support for python36 and suport for coverage output suppressing + pip install git+https://github.com/jenshnielsen/nose.git@matplotlibnose + + # We manually install humor sans using the package from Ubuntu 14.10. Unfortunatly humor sans is not + # availible in the Ubuntu version used by Travis but we can manually install the deb from a later + # version since is it basically just a .ttf file + # The current Travis Ubuntu image is to old to search .local/share/fonts so we store fonts in .fonts if [[ $BUILD_DOCS == true ]]; then - pip install $PRE numpydoc ipython jsonschema mistune - pip install -q $PRE linkchecker wget https://github.com/google/fonts/blob/master/ofl/felipa/Felipa-Regular.ttf?raw=true -O Felipa-Regular.ttf wget http://mirrors.kernel.org/ubuntu/pool/universe/f/fonts-humor-sans/fonts-humor-sans_1.0-1_all.deb mkdir -p tmp @@ -91,77 +116,87 @@ install: fc-cache -f -v else # Use the special local version of freetype for testing - cp .travis/setup.cfg . + cp ci/travis/setup.cfg . + fi; + - | + # Install matplotlib + pip install -e . + - | + # Installing basemap from github until it's back on pypi + # We have to install it after matplotlib to avoid pulling in MPL as + # a dependency + if [[ $BUILD_DOCS == true ]]; then + pip install git+https://github.com/matplotlib/basemap.git fi; - - - python setup.py install script: # The number of processes is hardcoded, because using too many causes the # Travis VM to run out of memory (since so many copies of inkscape and # ghostscript are running at the same time). - - echo Testing using $NPROC processes - | + echo Testing import of tkagg backend + MPLBACKEND="tkagg" python -c 'import matplotlib.pyplot as plt; print(plt.get_backend())' + echo The following args are passed to nose $NOSE_ARGS if [[ $BUILD_DOCS == false ]]; then export MPL_REPO_DIR=$PWD # needed for pep8-conformance test of the examples - gdb -return-child-result -batch -ex r -ex bt --args python tests.py --processes=$NPROC --process-timeout=300 $TEST_ARGS + if [[ $TRAVIS_OS_NAME == 'osx' ]]; then + python tests.py $NOSE_ARGS $TEST_ARGS + else + gdb -return-child-result -batch -ex r -ex bt --args python $PYTHON_ARGS tests.py $NOSE_ARGS $TEST_ARGS + fi else cd doc - python make.py html --small --warningsaserrors + python make.py html --small # We don't build the LaTeX docs here, so linkchecker will complain touch build/html/Matplotlib.pdf + # Linkchecker only works with python 2.7 for the time being + deactivate + source ~/virtualenv/python2.7/bin/activate + pip install pip --upgrade + # linkchecker is currently broken with requests 2.10.0 so force an earlier version + pip install $PRE requests==2.9.2 linkchecker linkchecker build/html/index.html fi + - rm -rf $HOME/.cache/matplotlib/tex.cache + - rm -rf $HOME/.cache/matplotlib/test_cache after_failure: - | - if [[ $BUILD_DOCS == false && $TRAVIS_PULL_REQUEST == false && $TRAVIS_REPO_SLUG == 'matplotlib/matplotlib' ]]; then - gem install travis-artifacts - cd $TRAVIS_BUILD_DIR/../tmp_test_dir - tar cjf result_images.tar.bz2 result_images - travis-artifacts upload --path result_images.tar.bz2 - echo https://s3.amazonaws.com/matplotlib-test-results/artifacts/${TRAVIS_BUILD_NUMBER}/${TRAVIS_JOB_NUMBER}/result_images.tar.bz2 - else - echo "The result images will only be uploaded if they are on the matplotlib/matplotlib repo - this is for security reasons to prevent arbitrary PRs echoing security details." - fi + - | + if [[ $BUILD_DOCS == false && $TRAVIS_PULL_REQUEST == false && $TRAVIS_REPO_SLUG == 'matplotlib/matplotlib' ]]; then + gem install travis-artifacts + cd $TRAVIS_BUILD_DIR/../tmp_test_dir + tar cjf result_images.tar.bz2 result_images + travis-artifacts upload --path result_images.tar.bz2 + echo https://s3.amazonaws.com/matplotlib-test-results/artifacts/${TRAVIS_BUILD_NUMBER}/${TRAVIS_JOB_NUMBER}/result_images.tar.bz2 + else + echo "The result images will only be uploaded if they are on the matplotlib/matplotlib repo - this is for security reasons to prevent arbitrary PRs echoing security details." + fi after_success: - | - if [[ $TRAVIS_PULL_REQUEST == false && $TRAVIS_REPO_SLUG == 'matplotlib/matplotlib' && $BUILD_DOCS == true && $TRAVIS_BRANCH == 'master' ]]; then - cd $TRAVIS_BUILD_DIR - echo "Uploading documentation" - openssl aes-256-cbc -K $encrypted_cc802e084cd0_key -iv $encrypted_cc802e084cd0_iv -in .travis/matplotlibDeployKey.enc -out .travis/matplotlibDeployKey -d - eval `ssh-agent -s` - chmod 600 .travis/matplotlibDeployKey - ssh-add .travis/matplotlibDeployKey - cd .. - git clone git@github.com:matplotlib/devdocs.git - cd devdocs - git checkout --orphan gh-pages - git reset --hard first_commit - cp -R ../matplotlib/doc/build/html/. . - touch .nojekyll - git config --global user.email "MatplotlibTravisBot@nomail" - git config --global user.name "MatplotlibTravisBot" - git config --global push.default simple - git add . - git commit -m "Docs build of $TRAVIS_COMMIT" - git push --set-upstream origin gh-pages --force - else - echo "Will only deploy docs build from matplotlib master branch" - fi - if [[ $TRAVIS_PULL_REQUEST == false ]] && \ - [[ $TRAVIS_REPO_SLUG == 'matplotlib/matplotlib' ]] && \ - [[ $TRAVIS_BRANCH == 'master' ]]; then + - | + if [[ $TRAVIS_PULL_REQUEST == false && $TRAVIS_REPO_SLUG == 'matplotlib/matplotlib' && $BUILD_DOCS == true && $TRAVIS_BRANCH == 'master' ]]; then cd $TRAVIS_BUILD_DIR - python .travis/travis_after_all.py - export $(cat .to_export_back) - if [ "$BUILD_LEADER" = "YES" ]; then - if [ "$BUILD_AGGREGATE_STATUS" = "others_succeeded" ]; then - echo "All Succeeded! Triggering OSX build..." - ./.travis/build_children.sh - else - echo "Some Failed; no OSX build" - fi - fi - fi + echo "Uploading documentation" + openssl aes-256-cbc -K $encrypted_cc802e084cd0_key -iv $encrypted_cc802e084cd0_iv -in ci/travis/matplotlibDeployKey.enc -out ci/travis/matplotlibDeployKey -d + eval `ssh-agent -s` + chmod 600 ci/travis/matplotlibDeployKey + ssh-add ci/travis/matplotlibDeployKey + cd .. + git clone git@github.com:matplotlib/devdocs.git + cd devdocs + git checkout --orphan gh-pages + git reset --hard first_commit + cp -R ../matplotlib/doc/build/html/. . + touch .nojekyll + git config --global user.email "MatplotlibTravisBot@nomail" + git config --global user.name "MatplotlibTravisBot" + git config --global push.default simple + git add . + git commit -m "Docs build of $TRAVIS_COMMIT" + git push --set-upstream origin gh-pages --force + else + echo "Will only deploy docs build from matplotlib master branch" + fi + if [[ $NOSE_ARGS="--with-coverage" ]]; then + coveralls + fi diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 912722bc4583..6bff60503296 100755 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -13,8 +13,8 @@ unicode_literals) import math -from matplotlib.externals import six -from matplotlib.externals.six.moves import map, xrange, zip, reduce +import six +from six.moves import map, xrange, zip, reduce import warnings from operator import itemgetter @@ -29,7 +29,8 @@ import matplotlib.scale as mscale from matplotlib.tri.triangulation import Triangulation import numpy as np -from matplotlib.colors import Normalize, colorConverter, LightSource +from matplotlib import colors as mcolors +from matplotlib.colors import Normalize, LightSource from . import art3d from . import proj3d @@ -745,19 +746,21 @@ def get_xlim3d(self): return self.xy_viewLim.intervalx get_xlim3d.__doc__ = maxes.Axes.get_xlim.__doc__ get_xlim = get_xlim3d - get_xlim.__doc__ += """ - .. versionchanged :: 1.1.0 - This function now correctly refers to the 3D x-limits - """ + if get_xlim.__doc__ is not None: + get_xlim.__doc__ += """ + .. versionchanged :: 1.1.0 + This function now correctly refers to the 3D x-limits + """ def get_ylim3d(self): return self.xy_viewLim.intervaly get_ylim3d.__doc__ = maxes.Axes.get_ylim.__doc__ get_ylim = get_ylim3d - get_ylim.__doc__ += """ - .. versionchanged :: 1.1.0 - This function now correctly refers to the 3D y-limits. - """ + if get_ylim.__doc__ is not None: + get_ylim.__doc__ += """ + .. versionchanged :: 1.1.0 + This function now correctly refers to the 3D y-limits. + """ def get_zlim3d(self): '''Get 3D z limits.''' @@ -779,22 +782,24 @@ def set_xscale(self, value, **kwargs) : self.xaxis._set_scale(value, **kwargs) self.autoscale_view(scaley=False, scalez=False) self._update_transScale() - set_xscale.__doc__ = maxes.Axes.set_xscale.__doc__ + """ + if maxes.Axes.set_xscale.__doc__ is not None: + set_xscale.__doc__ = maxes.Axes.set_xscale.__doc__ + """ - .. versionadded :: 1.1.0 - This function was added, but not tested. Please report any bugs. - """ + .. versionadded :: 1.1.0 + This function was added, but not tested. Please report any bugs. + """ def set_yscale(self, value, **kwargs) : self.yaxis._set_scale(value, **kwargs) self.autoscale_view(scalex=False, scalez=False) self._update_transScale() self.stale = True - set_yscale.__doc__ = maxes.Axes.set_yscale.__doc__ + """ + if maxes.Axes.set_yscale.__doc__ is not None: + set_yscale.__doc__ = maxes.Axes.set_yscale.__doc__ + """ - .. versionadded :: 1.1.0 - This function was added, but not tested. Please report any bugs. - """ + .. versionadded :: 1.1.0 + This function was added, but not tested. Please report any bugs. + """ @docstring.dedent_interpd def set_zscale(self, value, **kwargs) : @@ -1593,7 +1598,10 @@ def plot_surface(self, X, Y, Z, *args, **kwargs): if 'facecolors' in kwargs: fcolors = kwargs.pop('facecolors') else: - color = np.array(colorConverter.to_rgba(kwargs.pop('color', 'b'))) + color = kwargs.pop('color', None) + if color is None: + color = self._get_lines.get_next_color() + color = np.array(mcolors.to_rgba(color)) fcolors = None cmap = kwargs.get('cmap', None) @@ -1711,7 +1719,7 @@ def _shade_colors(self, color, normals): if len(shade[mask]) > 0: norm = Normalize(min(shade[mask]), max(shade[mask])) shade[~mask] = min(shade[mask]) - color = colorConverter.to_rgba_array(color) + color = mcolors.to_rgba_array(color) # shape of color should be (M, 4) (where M is number of faces) # shape of shade should be (M,) # colors should have final shape of (M, 4) @@ -1862,7 +1870,10 @@ def plot_trisurf(self, *args, **kwargs): had_data = self.has_data() # TODO: Support custom face colours - color = np.array(colorConverter.to_rgba(kwargs.pop('color', 'b'))) + color = kwargs.pop('color', None) + if color is None: + color = self._get_lines.get_next_color() + color = np.array(mcolors.to_rgba(color)) cmap = kwargs.get('cmap', None) norm = kwargs.pop('norm', None) @@ -1949,7 +1960,7 @@ def _3d_extend_contour(self, cset, stride=5): polyverts = [] normals = [] - nsteps = round(len(topverts[0]) / stride) + nsteps = np.round(len(topverts[0]) / stride) if nsteps <= 1: if len(topverts[0]) > 1: nsteps = 2 @@ -1957,9 +1968,9 @@ def _3d_extend_contour(self, cset, stride=5): continue stepsize = (len(topverts[0]) - 1) / (nsteps - 1) - for i in range(int(round(nsteps)) - 1): - i1 = int(round(i * stepsize)) - i2 = int(round((i + 1) * stepsize)) + for i in range(int(np.round(nsteps)) - 1): + i1 = int(np.round(i * stepsize)) + i2 = int(np.round((i + 1) * stepsize)) polyverts.append([topverts[0][i1], topverts[0][i2], botverts[0][i2], @@ -2211,7 +2222,7 @@ def add_collection3d(self, col, zs=0, zdir='z'): Axes.add_collection(self, col) - def scatter(self, xs, ys, zs=0, zdir='z', s=20, c='b', depthshade=True, + def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True, *args, **kwargs): ''' Create a scatter plot. @@ -2235,7 +2246,9 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c='b', depthshade=True, that *c* should not be a single numeric RGB or RGBA sequence because that is indistinguishable from an array of values to be colormapped. *c* can be a 2-D array in - which the rows are RGB or RGBA, however. + which the rows are RGB or RGBA, however, including the + case of a single row to specify the same color for + all points. *depthshade* Whether or not to shade the scatter markers to give @@ -2264,13 +2277,15 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c='b', depthshade=True, s = np.ma.ravel(s) # This doesn't have to match x, y in size. - cstr = cbook.is_string_like(c) or cbook.is_sequence_of_strings(c) - if not cstr: - c = np.asanyarray(c) - if c.size == xs.size: - c = np.ma.ravel(c) - - xs, ys, zs, s, c = cbook.delete_masked_points(xs, ys, zs, s, c) + if c is not None: + cstr = cbook.is_string_like(c) or cbook.is_sequence_of_strings(c) + if not cstr: + c = np.asanyarray(c) + if c.size == xs.size: + c = np.ma.ravel(c) + xs, ys, zs, s, c = cbook.delete_masked_points(xs, ys, zs, s, c) + else: + xs, ys, zs, s = cbook.delete_masked_points(xs, ys, zs, s) patches = Axes.scatter(self, xs, ys, s=s, c=c, *args, **kwargs) if not cbook.iterable(zs): @@ -2342,7 +2357,7 @@ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs): return patches - def bar3d(self, x, y, z, dx, dy, dz, color='b', + def bar3d(self, x, y, z, dx, dy, dz, color=None, zsort='average', *args, **kwargs): ''' Generate a 3D bar, or multiple bars. @@ -2442,7 +2457,7 @@ def bar3d(self, x, y, z, dx, dy, dz, color='b', facecolors.extend([c] * 6) else: # a single color specified, or face colors specified explicitly - facecolors = list(colorConverter.to_rgba_array(color)) + facecolors = list(mcolors.to_rgba_array(color)) if len(facecolors) < len(x): facecolors *= (6 * len(x)) @@ -2477,7 +2492,7 @@ def quiver(self, *args, **kwargs): *X*, *Y*, *Z*: The x, y and z coordinates of the arrow locations (default is - tip of arrow; see *pivot* kwarg) + tail of arrow; see *pivot* kwarg) *U*, *V*, *W*: The x, y and z components of the arrow vectors @@ -2500,6 +2515,12 @@ def quiver(self, *args, **kwargs): *pivot*: [ 'tail' | 'middle' | 'tip' ] The part of the arrow that is at the grid point; the arrow rotates about this point, hence the name *pivot*. + Default is 'tail' + + *normalize*: [False | True] + When True, all of the arrows will be the same length. This + defaults to False, where the arrows will be different lengths + depending on the values of u,v,w. Any additional keyword arguments are delegated to :class:`~matplotlib.collections.LineCollection` @@ -2508,6 +2529,7 @@ def quiver(self, *args, **kwargs): def calc_arrow(uvw, angle=15): """ To calculate the arrow head. uvw should be a unit vector. + We normalize it here: """ # get unit direction vector perpendicular to (u,v,w) norm = np.linalg.norm(uvw[:2]) @@ -2526,8 +2548,9 @@ def calc_arrow(uvw, angle=15): Rpos = np.array([[c+(x**2)*(1-c), x*y*(1-c), y*s], [y*x*(1-c), c+(y**2)*(1-c), -x*s], [-y*s, x*s, c]]) - # opposite rotation negates everything but the diagonal - Rneg = Rpos * (np.eye(3)*2 - 1) + # opposite rotation negates all the sin terms + Rneg = Rpos.copy() + Rneg[[0,1,2,2],[2,2,0,1]] = -Rneg[[0,1,2,2],[2,2,0,1]] # multiply them to get the rotated vector return Rpos.dot(uvw), Rneg.dot(uvw) @@ -2540,7 +2563,9 @@ def calc_arrow(uvw, angle=15): # arrow length ratio to the shaft length arrow_length_ratio = kwargs.pop('arrow_length_ratio', 0.3) # pivot point - pivot = kwargs.pop('pivot', 'tip') + pivot = kwargs.pop('pivot', 'tail') + # normalize + normalize = kwargs.pop('normalize', False) # handle args argi = 6 @@ -2599,9 +2624,12 @@ def calc_arrow(uvw, angle=15): norm = np.sqrt(np.sum(UVW**2, axis=1)) # If any row of UVW is all zeros, don't make a quiver for it - mask = norm > 1e-10 + mask = norm > 0 XYZ = XYZ[mask] - UVW = UVW[mask] / norm[mask].reshape((-1, 1)) + if normalize: + UVW = UVW[mask] / norm[mask].reshape((-1, 1)) + else: + UVW = UVW[mask] if len(XYZ) > 0: # compute the shaft lines all at once with an outer product