From d690f3f85e8cbebb8dee00a508e4937e90c2edb9 Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Wed, 12 Mar 2014 18:55:47 -0400 Subject: [PATCH 01/14] Created an accelerometer example. Uses garden graph to plot the values --- examples/accelerometer/accelerometerdemo.kv | 42 + examples/accelerometer/buildozer.spec | 173 ++++ .../libs/garden/garden.graph/README.md | 36 + .../libs/garden/garden.graph/__init__.py | 895 ++++++++++++++++++ .../libs/garden/garden.graph/graph.png | Bin 0 -> 50548 bytes .../libs/libs/garden/garden.graph/README.md | 36 + .../libs/libs/garden/garden.graph/__init__.py | 895 ++++++++++++++++++ .../libs/libs/garden/garden.graph/graph.png | Bin 0 -> 50548 bytes examples/accelerometer/main.py | 89 ++ examples/accelerometer/test.py | 26 + 10 files changed, 2192 insertions(+) create mode 100644 examples/accelerometer/accelerometerdemo.kv create mode 100644 examples/accelerometer/buildozer.spec create mode 100644 examples/accelerometer/libs/garden/garden.graph/README.md create mode 100644 examples/accelerometer/libs/garden/garden.graph/__init__.py create mode 100644 examples/accelerometer/libs/garden/garden.graph/graph.png create mode 100644 examples/accelerometer/libs/libs/garden/garden.graph/README.md create mode 100644 examples/accelerometer/libs/libs/garden/garden.graph/__init__.py create mode 100644 examples/accelerometer/libs/libs/garden/garden.graph/graph.png create mode 100644 examples/accelerometer/main.py create mode 100644 examples/accelerometer/test.py diff --git a/examples/accelerometer/accelerometerdemo.kv b/examples/accelerometer/accelerometerdemo.kv new file mode 100644 index 000000000..daa07e554 --- /dev/null +++ b/examples/accelerometer/accelerometerdemo.kv @@ -0,0 +1,42 @@ +#:kivy 1.8.0 +: + BoxLayout: + orientation: 'vertical' + padding: 20 + + Graph: + size_hint_y: 0.8 + id: graph_plot + xlabel:'Time' + ylabel:'Value' + y_grid_label: True + x_grid_label: True + padding: 5 + xmin:0 + xmax:100 + ymin:-15 + ymax:20 + + Button: + id: toggle_button + text: 'Start Accelerometer' + size_hint_y: 0.2 + on_press: root.do_toggle() + +: + size_hint: .7, .4 + title: "Error" + + BoxLayout: + orientation: 'vertical' + padding: 10 + spacing: 20 + + Label: + size_hint_y: 0.4 + text: "This feature has not yet been implemented in Plyer." + Button: + text: 'Dismiss' + size_hint_y: 0.4 + on_press: root.dismiss() + \ No newline at end of file diff --git a/examples/accelerometer/buildozer.spec b/examples/accelerometer/buildozer.spec new file mode 100644 index 000000000..b7f6e9d2b --- /dev/null +++ b/examples/accelerometer/buildozer.spec @@ -0,0 +1,173 @@ +[app] + +# (str) Title of your application +title = Plyer Accelerometer Example + +# (str) Package name +package.name = accelexample + +# (str) Package domain (needed for android/ios packaging) +package.domain = org.test + +# (str) Source code where the main.py live +source.dir = . + +# (list) Source files to include (let empty to include all the files) +source.include_exts = py,png,jpg,kv,atlas + +# (list) Source files to exclude (let empty to not exclude anything) +#source.exclude_exts = spec + +# (list) List of directory to exclude (let empty to not exclude anything) +#source.exclude_dirs = tests, bin + +# (list) List of exclusions using pattern matching +#source.exclude_patterns = license,images/*/*.jpg + +# (str) Application versioning (method 1) +# version.regex = __version__ = '(.*)' +# version.filename = %(source.dir)s/main.py + +# (str) Application versioning (method 2) +version = 1.0 + +# (list) Application requirements +requirements = plyer,kivy + +# (str) Presplash of the application +#presplash.filename = %(source.dir)s/data/presplash.png + +# (str) Icon of the application +#icon.filename = %(source.dir)s/data/icon.png + +# (str) Supported orientation (one of landscape, portrait or all) +orientation = portrait + +# (bool) Indicate if the application should be fullscreen or not +fullscreen = 0 + + +# +# Android specific +# + +# (list) Permissions +android.permissions = android.hardware.sensor.accelerometer + +# (int) Android API to use +#android.api = 14 + +# (int) Minimum API required (8 = Android 2.2 devices) +#android.minapi = 8 + +# (int) Android SDK version to use +#android.sdk = 21 + +# (str) Android NDK version to use +#android.ndk = 9 + +# (bool) Use --private data storage (True) or --dir public storage (False) +#android.private_storage = True + +# (str) Android NDK directory (if empty, it will be automatically downloaded.) +#android.ndk_path = + +# (str) Android SDK directory (if empty, it will be automatically downloaded.) +#android.sdk_path = + +# (str) Android entry point, default is ok for Kivy-based app +#android.entrypoint = org.renpy.android.PythonActivity + +# (list) List of Java .jar files to add to the libs so that pyjnius can access +# their classes. Don't add jars that you do not need, since extra jars can slow +# down the build process. Allows wildcards matching, for example: +# OUYA-ODK/libs/*.jar +#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar + +# (list) List of Java files to add to the android project (can be java or a +# directory containing the files) +#android.add_src = + +# (str) python-for-android branch to use, if not master, useful to try +# not yet merged features. +#android.branch = master + +# (str) OUYA Console category. Should be one of GAME or APP +# If you leave this blank, OUYA support will not be enabled +#android.ouya.category = GAME + +# (str) Filename of OUYA Console icon. It must be a 732x412 png image. +#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png + +# (str) XML file to include as an intent filters in tag +#android.manifest.intent_filters = + +# (list) Android additionnal libraries to copy into libs/armeabi +#android.add_libs_armeabi = libs/android/*.so + +# (bool) Indicate whether the screen should stay on +# Don't forget to add the WAKE_LOCK permission if you set this to True +#android.wakelock = False + +# (list) Android application meta-data to set (key=value format) +#android.meta_data = + +# (list) Android library project to add (will be added in the +# project.properties automatically.) +#android.library_references = + +# +# iOS specific +# + +# (str) Name of the certificate to use for signing the debug version +# Get a list of available identities: buildozer ios list_identities +#ios.codesign.debug = "iPhone Developer: ()" + +# (str) Name of the certificate to use for signing the release version +#ios.codesign.release = %(ios.codesign.debug)s + + +[buildozer] + +# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) +log_level = 2 + + +# ----------------------------------------------------------------------------- +# List as sections +# +# You can define all the "list" as [section:key]. +# Each line will be considered as a option to the list. +# Let's take [app] / source.exclude_patterns. +# Instead of doing: +# +# [app] +# source.exclude_patterns = license,data/audio/*.wav,data/images/original/* +# +# This can be translated into: +# +# [app:source.exclude_patterns] +# license +# data/audio/*.wav +# data/images/original/* +# + + +# ----------------------------------------------------------------------------- +# Profiles +# +# You can extend section / key with a profile +# For example, you want to deploy a demo version of your application without +# HD content. You could first change the title to add "(demo)" in the name +# and extend the excluded directories to remove the HD content. +# +# [app@demo] +# title = My Application (demo) +# +# [app:source.exclude_patterns@demo] +# images/hd/* +# +# Then, invoke the command line with the "demo" profile: +# +# buildozer --profile demo android debug diff --git a/examples/accelerometer/libs/garden/garden.graph/README.md b/examples/accelerometer/libs/garden/garden.graph/README.md new file mode 100644 index 000000000..3aee2fbb2 --- /dev/null +++ b/examples/accelerometer/libs/garden/garden.graph/README.md @@ -0,0 +1,36 @@ +Graph +====== + +The `Graph` widget is a widget for displaying plots. It supports +drawing multiple plot with different colors on the Graph. It also supports +a title, ticks, labeled ticks, grids and a log or linear representation on +both the x and y axis, independently. + +To display a plot. First create a graph which will function as a "canvas" for +the plots. Then create plot objects e.g. MeshLinePlot and add them to the +graph. + +To create a graph with x-axis between 0-100, y-axis between -1 to 1, x and y +labels of and X and Y, respectively, x major and minor ticks every 25, 5 units, +respectively, y major ticks every 1 units, full x and y grids and with +a red line plot containing a sin wave on this range:: + + from kivy.garden.graph import Graph, MeshLinePlot + graph = Graph(xlabel='X', ylabel='Y', x_ticks_minor=5, + x_ticks_major=25, y_ticks_major=1, + y_grid_label=True, x_grid_label=True, padding=5, + x_grid=True, y_grid=True, xmin=-0, xmax=100, ymin=-1, ymax=1) + plot = MeshLinePlot(color=[1, 0, 0, 1]) + plot.points = [(x, sin(x / 10.)) for x in xrange(0, 101)] + graph.add_plot(plot) + +The `MeshLinePlot` plot is a particular plot which draws a set of points using +a mesh object. The points are given as a list of tuples, with each tuple +being a (x, y) coordinate in the graph's units. + +You can create different types of plots other than `MeshLinePlot` by inheriting +from the `Plot` class and implementing the required functions. The `Graph` object +provides a "canvas" to which a Plot's instructions are added. The plot object +is responsible for updating these instructions to show within the bounding +box of the graph the proper plot. The Graph notifies the Plot when it needs +to be redrawn due to changes. See the `MeshLinePlot` class for how it is done. diff --git a/examples/accelerometer/libs/garden/garden.graph/__init__.py b/examples/accelerometer/libs/garden/garden.graph/__init__.py new file mode 100644 index 000000000..dda186ee4 --- /dev/null +++ b/examples/accelerometer/libs/garden/garden.graph/__init__.py @@ -0,0 +1,895 @@ +''' +Graph +====== + +The :class:`Graph` widget is a widget for displaying plots. It supports +drawing multiple plot with different colors on the Graph. It also supports +a title, ticks, labeled ticks, grids and a log or linear representation on +both the x and y axis, independently. + +To display a plot. First create a graph which will function as a "canvas" for +the plots. Then create plot objects e.g. MeshLinePlot and add them to the +graph. + +To create a graph with x-axis between 0-100, y-axis between -1 to 1, x and y +labels of and X and Y, respectively, x major and minor ticks every 25, 5 units, +respectively, y major ticks every 1 units, full x and y grids and with +a red line plot containing a sin wave on this range:: + + from kivy.garden.graph import Graph, MeshLinePlot + graph = Graph(xlabel='X', ylabel='Y', x_ticks_minor=5, + x_ticks_major=25, y_ticks_major=1, + y_grid_label=True, x_grid_label=True, padding=5, + x_grid=True, y_grid=True, xmin=-0, xmax=100, ymin=-1, ymax=1) + plot = MeshLinePlot(color=[1, 0, 0, 1]) + plot.points = [(x, sin(x / 10.)) for x in xrange(0, 101)] + graph.add_plot(plot) + +The MeshLinePlot plot is a particular plot which draws a set of points using +a mesh object. The points are given as a list of tuples, with each tuple +being a (x, y) coordinate in the graph's units. + +You can create different types of plots other than MeshLinePlot by inheriting +from the Plot class and implementing the required functions. The Graph object +provides a "canvas" to which a Plot's instructions are added. The plot object +is responsible for updating these instructions to show within the bounding +box of the graph the proper plot. The Graph notifies the Plot when it needs +to be redrawn due to changes. See the MeshLinePlot class for how it is done. + +.. note:: + + The graph uses a stencil view to clip the plots to the graph display area. + As with the stencil graphics instructions, you cannot stack more than 8 + stencil-aware widgets. + +''' + +__all__ = ('Graph', 'Plot', 'MeshLinePlot', 'MeshStemPlot') + +from math import radians +from kivy.uix.widget import Widget +from kivy.uix.label import Label +from kivy.uix.stencilview import StencilView +from kivy.properties import NumericProperty, BooleanProperty,\ + BoundedNumericProperty, StringProperty, ListProperty, ObjectProperty,\ + DictProperty, AliasProperty +from kivy.clock import Clock +from kivy.graphics import Mesh, Color +from kivy.graphics.transformation import Matrix +from kivy.event import EventDispatcher +from kivy.lang import Builder +from kivy import metrics +from math import log10, floor, ceil +from decimal import Decimal + +Builder.load_string(''' +#:kivy 1.1.0 + +: + canvas.before: + PushMatrix + MatrixInstruction: + matrix: self.transform + canvas.after: + PopMatrix + +''') + + +class RotateLabel(Label): + + transform = ObjectProperty(Matrix()) + + +class Graph(Widget): + '''Graph class, see module documentation for more information. + ''' + + # triggers a full reload of graphics + _trigger = ObjectProperty(None) + # triggers only a repositioning of objects due to size/pos updates + _trigger_size = ObjectProperty(None) + # holds widget with the x-axis label + _xlabel = ObjectProperty(None) + # holds widget with the y-axis label + _ylabel = ObjectProperty(None) + # holds all the x-axis tick mark labels + _x_grid_label = ListProperty([]) + # holds all the y-axis tick mark labels + _y_grid_label = ListProperty([]) + # holds the stencil view that clipse the plots to graph area + _plot_area = ObjectProperty(None) + # the mesh drawing all the ticks/grids + _mesh = ObjectProperty(None) + # the mesh which draws the surrounding rectangle + _mesh_rect = ObjectProperty(None) + # a list of locations of major and minor ticks. The values are not + # but is in the axis min - max range + _ticks_majorx = ListProperty([]) + _ticks_minorx = ListProperty([]) + _ticks_majory = ListProperty([]) + _ticks_minory = ListProperty([]) + + def __init__(self, **kwargs): + super(Graph, self).__init__(**kwargs) + + self._mesh = Mesh(mode='lines') + self._mesh_rect = Mesh(mode='line_strip') + val = 0.25 + self.canvas.add(Color(1 * val, 1 * val, 1 * val)) + self.canvas.add(self._mesh) + self.canvas.add(Color(1, 1, 1)) + self.canvas.add(self._mesh_rect) + mesh = self._mesh_rect + mesh.vertices = [0] * (5 * 4) + mesh.indices = [k for k in xrange(5)] + + self._plot_area = StencilView() + self.add_widget(self._plot_area) + + self._trigger = Clock.create_trigger(self._redraw_all) + self._trigger_size = Clock.create_trigger(self._redraw_size) + + self.bind(center=self._trigger_size, padding=self._trigger_size, + font_size=self._trigger_size, plots=self._trigger_size, + x_grid=self._trigger_size, y_grid=self._trigger_size, + draw_border=self._trigger_size) + self.bind(xmin=self._trigger, xmax=self._trigger, + xlog=self._trigger, x_ticks_major=self._trigger, + x_ticks_minor=self._trigger, + xlabel=self._trigger, x_grid_label=self._trigger, + ymin=self._trigger, ymax=self._trigger, + ylog=self._trigger, y_ticks_major=self._trigger, + y_ticks_minor=self._trigger, + ylabel=self._trigger, y_grid_label=self._trigger) + self._trigger() + + def _get_ticks(self, major, minor, log, s_min, s_max): + if major and s_max > s_min: + if log: + s_min = log10(s_min) + s_max = log10(s_max) + # count the decades in min - max. This is in actual decades, + # not logs. + n_decades = floor(s_max - s_min) + # for the fractional part of the last decade, we need to + # convert the log value, x, to 10**x but need to handle + # differently if the last incomplete decade has a decade + # boundary in it + if floor(s_min + n_decades) != floor(s_max): + n_decades += 1 - (10 ** (s_min + n_decades + 1) - 10 ** + s_max) / 10 ** floor(s_max + 1) + else: + n_decades += ((10 ** s_max - 10 ** (s_min + n_decades)) / + 10 ** floor(s_max + 1)) + # this might be larger than what is needed, but we delete + # excess later + n_ticks_major = n_decades / float(major) + n_ticks = int(floor(n_ticks_major * (minor if minor >= + 1. else 1.0))) + 2 + # in decade multiples, e.g. 0.1 of the decade, the distance + # between ticks + decade_dist = major / float(minor if minor else 1.0) + + points_minor = [0] * n_ticks + points_major = [0] * n_ticks + k = 0 # position in points major + k2 = 0 # position in points minor + # because each decade is missing 0.1 of the decade, if a tick + # falls in < min_pos skip it + min_pos = 0.1 - 0.00001 * decade_dist + s_min_low = floor(s_min) + # first real tick location. value is in fractions of decades + # from the start we have to use decimals here, otherwise + # floating point inaccuracies results in bad values + start_dec = ceil((10 ** Decimal(s_min - s_min_low - 1)) / + Decimal(decade_dist)) * decade_dist + count_min = (0 if not minor else + floor(start_dec / decade_dist) % minor) + start_dec += s_min_low + count = 0 # number of ticks we currently have passed start + while True: + # this is the current position in decade that we are. + # e.g. -0.9 means that we're at 0.1 of the 10**ceil(-0.9) + # decade + pos_dec = start_dec + decade_dist * count + pos_dec_low = floor(pos_dec) + diff = pos_dec - pos_dec_low + zero = abs(diff) < 0.001 * decade_dist + if zero: + # the same value as pos_dec but in log scale + pos_log = pos_dec_low + else: + pos_log = log10((pos_dec - pos_dec_low + ) * 10 ** ceil(pos_dec)) + if pos_log > s_max: + break + count += 1 + if zero or diff >= min_pos: + if minor and not count_min % minor: + points_major[k] = pos_log + k += 1 + else: + points_minor[k2] = pos_log + k2 += 1 + count_min += 1 + #n_ticks = len(points) + else: + # distance between each tick + tick_dist = major / float(minor if minor else 1.0) + n_ticks = int(floor((s_max - s_min) / tick_dist) + 1) + points_major = [0] * int(floor((s_max - s_min) / float(major)) + + 1) + points_minor = [0] * (n_ticks - len(points_major) + 1) + k = 0 # position in points major + k2 = 0 # position in points minor + for m in xrange(0, n_ticks): + if minor and m % minor: + points_minor[k2] = m * tick_dist + s_min + k2 += 1 + else: + points_major[k] = m * tick_dist + s_min + k += 1 + del points_major[k:] + del points_minor[k2:] + else: + points_major = [] + points_minor = [] + return points_major, points_minor + + def _update_labels(self): + xlabel = self._xlabel + ylabel = self._ylabel + x = self.x + y = self.y + width = self.width + height = self.height + padding = self.padding + x_next = padding + x + y_next = padding + y + xextent = x + width + yextent = y + height + ymin = self.ymin + ymax = self.ymax + xmin = self.xmin + precision = self.precision + x_overlap = False + y_overlap = False + # set up x and y axis labels + if xlabel: + xlabel.text = self.xlabel + xlabel.texture_update() + xlabel.size = xlabel.texture_size + xlabel.pos = (x + width / 2. - xlabel.width / 2., padding + y) + y_next += padding + xlabel.height + if ylabel: + ylabel.text = self.ylabel + ylabel.texture_update() + ylabel.size = ylabel.texture_size + ylabel.x = padding + x - (ylabel.width / 2. - ylabel.height / 2.) + x_next += padding + ylabel.height + xpoints = self._ticks_majorx + xlabels = self._x_grid_label + xlabel_grid = self.x_grid_label + ylabel_grid = self.y_grid_label + ypoints = self._ticks_majory + ylabels = self._y_grid_label + # now x and y tick mark labels + if len(ylabels) and ylabel_grid: + # horizontal size of the largest tick label, to have enough room + ylabels[0].text = precision % ypoints[0] + ylabels[0].texture_update() + y1 = ylabels[0].texture_size + y_start = y_next + (padding + y1[1] if len(xlabels) and xlabel_grid + else 0) +\ + (padding + y1[1] if not y_next else 0) + yextent = y + height - padding - y1[1] / 2. + if self.ylog: + ymax = log10(ymax) + ymin = log10(ymin) + ratio = (yextent - y_start) / float(ymax - ymin) + y_start -= y1[1] / 2. + func = (lambda x: 10 ** x) if self.ylog else lambda x: x + y1 = y1[0] + for k in xrange(len(ylabels)): + ylabels[k].text = precision % func(ypoints[k]) + ylabels[k].texture_update() + ylabels[k].size = ylabels[k].texture_size + y1 = max(y1, ylabels[k].texture_size[0]) + ylabels[k].pos = (x_next, y_start + (ypoints[k] - ymin) * + ratio) + if len(ylabels) > 1 and ylabels[0].top > ylabels[1].y: + y_overlap = True + else: + x_next += y1 + padding + if len(xlabels) and xlabel_grid: + func = log10 if self.xlog else lambda x: x + # find the distance from the end that'll fit the last tick label + xlabels[0].text = precision % func(xpoints[-1]) + xlabels[0].texture_update() + xextent = x + width - xlabels[0].texture_size[0] / 2. - padding + # find the distance from the start that'll fit the first tick label + if not x_next: + xlabels[0].text = precision % func(xpoints[0]) + xlabels[0].texture_update() + x_next = padding + xlabels[0].texture_size[0] / 2. + xmin = func(xmin) + ratio = (xextent - x_next) / float(func(self.xmax) - xmin) + func = (lambda x: 10 ** x) if self.xlog else lambda x: x + right = -1 + for k in xrange(len(xlabels)): + xlabels[k].text = precision % func(xpoints[k]) + # update the size so we can center the labels on ticks + xlabels[k].texture_update() + xlabels[k].size = xlabels[k].texture_size + xlabels[k].pos = (x_next + (xpoints[k] - xmin) * ratio - + xlabels[k].texture_size[0] / 2., y_next) + if xlabels[k].x < right: + x_overlap = True + break + right = xlabels[k].right + if not x_overlap: + y_next += padding + xlabels[0].texture_size[1] + # now re-center the x and y axis labels + if xlabel: + xlabel.x = x_next + (xextent - x_next) / 2. - xlabel.width / 2. + if ylabel: + ylabel.y = y_next + (yextent - y_next) / 2. - ylabel.height / 2. + t = Matrix().translate(ylabel.center[0], ylabel.center[1], 0) + t = t.multiply(Matrix().rotate(-radians(270), 0, 0, 1)) + ylabel.transform = t.multiply(Matrix().translate(-ylabel.center[0], + -ylabel.center[1], + 0)) + if x_overlap: + for k in xrange(len(xlabels)): + xlabels[k].text = '' + if y_overlap: + for k in xrange(len(ylabels)): + ylabels[k].text = '' + return x_next, y_next, xextent, yextent + + def _update_ticks(self, size): + # re-compute the positions of the bounding rectangle + mesh = self._mesh_rect + vert = mesh.vertices + if self.draw_border: + vert[0] = size[0] + vert[1] = size[1] + vert[4] = size[2] + vert[5] = size[1] + vert[8] = size[2] + vert[9] = size[3] + vert[12] = size[0] + vert[13] = size[3] + vert[16] = size[0] + vert[17] = size[1] + else: + vert[0:18] = [0 for k in xrange(18)] + mesh.vertices = vert + # re-compute the positions of the x/y axis ticks + mesh = self._mesh + vert = mesh.vertices + start = 0 + xpoints = self._ticks_majorx + ypoints = self._ticks_majory + ylog = self.ylog + xlog = self.xlog + xmin = self.xmin + xmax = self.xmax + if xlog: + xmin = log10(xmin) + xmax = log10(xmax) + ymin = self.ymin + ymax = self.ymax + if ylog: + xmin = log10(ymin) + ymax = log10(ymax) + if len(xpoints): + top = size[3] if self.x_grid else metrics.dp(12) + size[1] + ratio = (size[2] - size[0]) / float(xmax - xmin) + for k in xrange(start, len(xpoints) + start): + vert[k * 8] = size[0] + (xpoints[k - start] - xmin) * ratio + vert[k * 8 + 1] = size[1] + vert[k * 8 + 4] = vert[k * 8] + vert[k * 8 + 5] = top + start += len(xpoints) + if len(ypoints): + top = size[2] if self.y_grid else metrics.dp(12) + size[0] + ratio = (size[3] - size[1]) / float(ymax - ymin) + for k in xrange(start, len(ypoints) + start): + vert[k * 8 + 1] = size[1] + (ypoints[k - start] - ymin) * ratio + vert[k * 8 + 5] = vert[k * 8 + 1] + vert[k * 8] = size[0] + vert[k * 8 + 4] = top + mesh.vertices = vert + + def _update_plots(self, size): + ylog = self.ylog + xlog = self.xlog + xmin = self.xmin + xmax = self.xmax + ymin = self.ymin + ymax = self.ymax + for plot in self.plots: + plot._update(xlog, xmin, xmax, ylog, ymin, ymax, size) + + def _redraw_all(self, *args): + # add/remove all the required labels + font_size = self.font_size + if self.xlabel: + if not self._xlabel: + xlabel = Label(font_size=font_size) + self.add_widget(xlabel) + self._xlabel = xlabel + else: + xlabel = self._xlabel + if xlabel: + self.remove_widget(xlabel) + self._xlabel = None + grids = self._x_grid_label + xpoints_major, xpoints_minor = self._get_ticks(self.x_ticks_major, + self.x_ticks_minor, + self.xlog, self.xmin, + self.xmax) + self._ticks_majorx = xpoints_major + self._ticks_minorx = xpoints_minor + if not self.x_grid_label: + n_labels = 0 + else: + n_labels = len(xpoints_major) + for k in xrange(n_labels, len(grids)): + self.remove_widget(grids[k]) + del grids[n_labels:] + grid_len = len(grids) + grids.extend([None] * (n_labels - len(grids))) + for k in xrange(grid_len, n_labels): + grids[k] = Label(font_size=font_size) + self.add_widget(grids[k]) + + if self.ylabel: + if not self._ylabel: + ylabel = RotateLabel(font_size=font_size) + self.add_widget(ylabel) + self._ylabel = ylabel + else: + ylabel = self._ylabel + if ylabel: + self.remove_widget(ylabel) + self._ylabel = None + grids = self._y_grid_label + ypoints_major, ypoints_minor = self._get_ticks(self.y_ticks_major, + self.y_ticks_minor, + self.ylog, self.ymin, + self.ymax) + self._ticks_majory = ypoints_major + self._ticks_minory = ypoints_minor + if not self.y_grid_label: + n_labels = 0 + else: + n_labels = len(ypoints_major) + for k in xrange(n_labels, len(grids)): + self.remove_widget(grids[k]) + del grids[n_labels:] + grid_len = len(grids) + grids.extend([None] * (n_labels - len(grids))) + for k in xrange(grid_len, n_labels): + grids[k] = Label(font_size=font_size) + self.add_widget(grids[k]) + + mesh = self._mesh + n_points = (len(xpoints_major) + len(xpoints_minor) + + len(ypoints_major) + len(ypoints_minor)) + mesh.vertices = [0] * (n_points * 8) + mesh.indices = [k for k in xrange(n_points * 2)] + self._redraw_size() + + def _redraw_size(self, *args): + # size a 4-tuple describing the bounding box in which we can draw + # graphs, it's (x0, y0, x1, y1), which correspond with the bottom left + # and top right corner locations, respectively + size = self._update_labels() + self._plot_area.pos = (size[0], size[1]) + self._plot_area.size = (size[2] - size[0], size[3] - size[1]) + self._update_ticks(size) + self._update_plots(size) + + def add_plot(self, plot): + '''Add a new plot to this graph. + + :Parameters: + `plot`: + Plot to add to this graph. + + >>> graph = Graph() + >>> plot = MeshLinePlot(mode='line_strip', color=[1, 0, 0, 1]) + >>> plot.points = [(x / 10., sin(x / 50.)) for x in xrange(-0, 101)] + >>> graph.add_plot(plot) + ''' + area = self._plot_area + for group in plot._get_drawings(): + area.canvas.add(group) + self.plots = self.plots + [plot] + + def remove_plot(self, plot): + '''Remove a plot from this graph. + + :Parameters: + `plot`: + Plot to remove from this graph. + + >>> graph = Graph() + >>> plot = MeshLinePlot(mode='line_strip', color=[1, 0, 0, 1]) + >>> plot.points = [(x / 10., sin(x / 50.)) for x in xrange(-0, 101)] + >>> graph.add_plot(plot) + >>> graph.remove_plot(plot) + ''' + self._plot_area.canvas.remove_group(plot._get_group()) + self.plots.remove(plot) + + xmin = NumericProperty(0.) + '''The x-axis minimum value. + + If :data:`xlog` is True, xmin must be larger than zero. + + :data:`xmin` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. + ''' + + xmax = NumericProperty(100.) + '''The x-axis maximum value, larger than xmin. + + :data:`xmax` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. + ''' + + xlog = BooleanProperty(False) + '''Determines whether the x-axis should be displayed logarithmically (True) + or linearly (False). + + :data:`xlog` is a :class:`~kivy.properties.BooleanProperty`, defaults + to False. + ''' + + x_ticks_major = BoundedNumericProperty(0, min=0) + '''Distance between major tick marks on the x-axis. + + Determines the distance between the major tick marks. Major tick marks + start from min and re-occur at every ticks_major until :data:`xmax`. + If :data:`xmax` doesn't overlap with a integer multiple of ticks_major, + no tick will occur at :data:`xmax`. Zero indicates no tick marks. + + If :data:`xlog` is true, then this indicates the distance between ticks + in multiples of current decade. E.g. if :data:`xmin` is 0.1 and + ticks_major is 0.1, it means there will be a tick at every 10th of the + decade, i.e. 0.1 ... 0.9, 1, 2... If it is 0.3, the ticks will occur at + 0.1, 0.3, 0.6, 0.9, 2, 5, 8, 10. You'll notice that it went from 8 to 10 + instead of to 20, that's so that we can say 0.5 and have ticks at every + half decade, e.g. 0.1, 0.5, 1, 5, 10, 50... Similarly, if ticks_major is + 1.5, there will be ticks at 0.1, 5, 100, 5,000... Also notice, that there's + always a major tick at the start. Finally, if e.g. :data:`xmin` is 0.6 + and this 0.5 there will be ticks at 0.6, 1, 5... + + :data:`x_ticks_major` is a + :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. + ''' + + x_ticks_minor = BoundedNumericProperty(0, min=0) + '''The number of sub-intervals that divide x_ticks_major. + + Determines the number of sub-intervals into which ticks_major is divided, + if non-zero. The actual number of minor ticks between the major ticks is + ticks_minor - 1. Only used if ticks_major is non-zero. If there's no major + tick at xmax then the number of minor ticks after the last major + tick will be however many ticks fit until xmax. + + If self.xlog is true, then this indicates the number of intervals the + distance between major ticks is divided. The result is the number of + multiples of decades between ticks. I.e. if ticks_minor is 10, then if + ticks_major is 1, there will be ticks at 0.1, 0.2...0.9, 1, 2, 3... If + ticks_major is 0.3, ticks will occur at 0.1, 0.12, 0.15, 0.18... Finally, + as is common, if ticks major is 1, and ticks minor is 5, there will be + ticks at 0.1, 0.2, 0.4... 0.8, 1, 2... + + :data:`x_ticks_minor` is a + :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. + ''' + + x_grid = BooleanProperty(False) + '''Determines whether the x-axis has tick marks or a full grid. + + If :data:`x_ticks_major` is non-zero, then if x_grid is False tick marks + will be displayed at every major tick. If x_grid is True, instead of ticks, + a vertical line will be displayed at every major tick. + + :data:`x_grid` is a :class:`~kivy.properties.BooleanProperty`, defaults + to False. + ''' + + x_grid_label = BooleanProperty(False) + '''Whether labels should be displayed beneath each major tick. If true, + each major tick will have a label containing the axis value. + + :data:`x_grid_label` is a :class:`~kivy.properties.BooleanProperty`, + defaults to False. + ''' + + xlabel = StringProperty('') + '''The label for the x-axis. If not empty it is displayed in the center of + the axis. + + :data:`xlabel` is a :class:`~kivy.properties.StringProperty`, + defaults to ''. + ''' + + ymin = NumericProperty(0.) + '''The y-axis minimum value. + + If :data:`ylog` is True, ymin must be larger than zero. + + :data:`ymin` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. + ''' + + ymax = NumericProperty(100.) + '''The y-axis maximum value, larger than ymin. + + :data:`ymax` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. + ''' + + ylog = BooleanProperty(False) + '''Determines whether the y-axis should be displayed logarithmically (True) + or linearly (False). + + :data:`ylog` is a :class:`~kivy.properties.BooleanProperty`, defaults + to False. + ''' + + y_ticks_major = BoundedNumericProperty(0, min=0) + '''Distance between major tick marks. See :data:`x_ticks_major`. + + :data:`y_ticks_major` is a + :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. + ''' + + y_ticks_minor = BoundedNumericProperty(0, min=0) + '''The number of sub-intervals that divide ticks_major. + See :data:`x_ticks_minor`. + + :data:`y_ticks_minor` is a + :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. + ''' + + y_grid = BooleanProperty(False) + '''Determines whether the y-axis has tick marks or a full grid. See + :data:`x_grid`. + + :data:`y_grid` is a :class:`~kivy.properties.BooleanProperty`, defaults + to False. + ''' + + y_grid_label = BooleanProperty(False) + '''Whether labels should be displayed beneath each major tick. If true, + each major tick will have a label containing the axis value. + + :data:`y_grid_label` is a :class:`~kivy.properties.BooleanProperty`, + defaults to False. + ''' + + ylabel = StringProperty('') + '''The label for the y-axis. If not empty it is displayed in the center of + the axis. + + :data:`ylabel` is a :class:`~kivy.properties.StringProperty`, + defaults to ''. + ''' + + padding = NumericProperty('5dp') + '''Padding distances between the labels, titles and graph, as well between + the widget and the objects near the boundaries. + + :data:`padding` is a :class:`~kivy.properties.NumericProperty`, defaults + to 5dp. + ''' + + font_size = NumericProperty('15sp') + '''Font size of the labels. + + :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, defaults + to 15sp. + ''' + + precision = StringProperty('%g') + '''Determines the numerical precision of the tick mark labels. This value + governs how the numbers are converted into string representation. Accepted + values are those listed in Python's manual in the + "String Formatting Operations" section. + + :data:`precision` is a :class:`~kivy.properties.StringProperty`, defaults + to '%g'. + ''' + + draw_border = BooleanProperty(True) + '''Whether a border is drawn around the canvas of the graph where the + plots are displayed. + + :data:`draw_border` is a :class:`~kivy.properties.BooleanProperty`, + defaults to True. + ''' + + plots = ListProperty([]) + '''Holds a list of all the plots in the graph. To add and remove plots + from the graph use :data:`add_plot` and :data:`add_plot`. Do not add + directly edit this list. + + :data:`plots` is a :class:`~kivy.properties.ListProperty`, + defaults to []. + ''' + + +class Plot(EventDispatcher): + '''Plot class, see module documentation for more information. + ''' + + # this function is called by graph whenever any of the parameters + # change. The plot should be recalculated then. + # log, min, max indicate the axis settings. + # size a 4-tuple describing the bounding box in which we can draw + # graphs, it's (x0, y0, x1, y1), which correspond with the bottom left + # and top right corner locations, respectively. + def _update(self, xlog, xmin, xmax, ylog, ymin, ymax, size): + pass + + # returns a string which is unique and is the group name given to all the + # instructions returned by _get_drawings. Graph uses this to remove + # these instructions when needed. + def _get_group(self): + return '' + + # returns a list of canvas instructions that will be added to the graph's + # canvas. These instructions must belong to a group as described + # in _get_group. + def _get_drawings(self): + return [] + + +class MeshLinePlot(Plot): + '''MeshLinePlot class which displays a set of points similar to a mesh. + ''' + + # mesh which forms the plot + _mesh = ObjectProperty(None) + # color of the plot + _color = ObjectProperty(None) + _trigger = ObjectProperty(None) + # most recent values of the params used to draw the plot + _params = DictProperty({'xlog': False, 'xmin': 0, 'xmax': 100, + 'ylog': False, 'ymin': 0, 'ymax': 100, + 'size': (0, 0, 0, 0)}) + + def __init__(self, **kwargs): + self._color = Color(1, 1, 1, group='LinePlot%d' % id(self)) + self._mesh = Mesh(mode='line_strip', group='LinePlot%d' % id(self)) + super(MeshLinePlot, self).__init__(**kwargs) + + self._trigger = Clock.create_trigger(self._redraw) + self.bind(_params=self._trigger, points=self._trigger) + + def _update(self, xlog, xmin, xmax, ylog, ymin, ymax, size): + self._params = {'xlog': xlog, 'xmin': xmin, 'xmax': xmax, 'ylog': ylog, + 'ymin': ymin, 'ymax': ymax, 'size': size} + + def _redraw(self, *args): + points = self.points + mesh = self._mesh + vert = mesh.vertices + ind = mesh.indices + params = self._params + funcx = log10 if params['xlog'] else lambda x: x + funcy = log10 if params['ylog'] else lambda x: x + xmin = funcx(params['xmin']) + ymin = funcy(params['ymin']) + diff = len(points) - len(vert) / 4 + size = params['size'] + ratiox = (size[2] - size[0]) / float(funcx(params['xmax']) - xmin) + ratioy = (size[3] - size[1]) / float(funcy(params['ymax']) - ymin) + if diff < 0: + del vert[4 * len(points):] + del ind[len(points):] + elif diff > 0: + ind.extend(xrange(len(ind), len(ind) + diff)) + vert.extend([0] * (diff * 4)) + for k in xrange(len(points)): + vert[k * 4] = (funcx(points[k][0]) - xmin) * ratiox + size[0] + vert[k * 4 + 1] = (funcy(points[k][1]) - ymin) * ratioy + size[1] + mesh.vertices = vert + + def _get_group(self): + return 'LinePlot%d' % id(self) + + def _get_drawings(self): + return [self._color, self._mesh] + + def _set_mode(self, value): + self._mesh.mode = value + mode = AliasProperty(lambda self: self._mesh.mode, _set_mode) + '''VBO Mode used for drawing the points. Can be one of: 'points', + 'line_strip', 'line_loop', 'lines', 'triangle_strip', 'triangle_fan'. + See :class:`~kivy.graphics.Mesh` for more details. + + Defaults to 'line_strip'. + ''' + + def _set_color(self, value): + self._color.rgba = value + color = AliasProperty(lambda self: self._color.rgba, _set_color) + '''Plot color, in the format [r, g, b, a] with values between 0-1. + + Defaults to [1, 1, 1, 1]. + ''' + + points = ListProperty([]) + '''List of x, y points to be displayed in the plot. + + The elements of points are 2-tuples, (x, y). The points are displayed + based on the mode setting. + + :data:`points` is a :class:`~kivy.properties.ListProperty`, defaults to + []. + ''' + +class MeshStemPlot(MeshLinePlot): + '''MeshStemPlot uses the MeshLinePlot class to draw a stem plot. The data + provided is graphed from origin to the data point. + ''' + + def _redraw(self, *args): + points = self.points + mesh = self._mesh + self._mesh.mode = 'lines' + vert = mesh.vertices + ind = mesh.indices + params = self._params + funcx = log10 if params['xlog'] else lambda x: x + funcy = log10 if params['ylog'] else lambda x: x + xmin = funcx(params['xmin']) + ymin = funcy(params['ymin']) + diff = len(points) * 2 - len(vert) / 4 + size = params['size'] + ratiox = (size[2] - size[0]) / float(funcx(params['xmax']) - xmin) + ratioy = (size[3] - size[1]) / float(funcy(params['ymax']) - ymin) + if diff < 0: + del vert[4 * len(points):] + del ind[len(points):] + elif diff > 0: + ind.extend(xrange(len(ind), len(ind) + diff)) + vert.extend([0] * (diff * 4)) + for k in xrange(len(points)): + vert[k * 8] = (funcx(points[k][0]) - xmin) * ratiox + size[0] + vert[k * 8 + 1] = (0 - ymin) * ratioy + size[1] + vert[k * 8 + 4] = (funcx(points[k][0]) - xmin) * ratiox + size[0] + vert[k * 8 + 5] = (funcy(points[k][1]) - ymin) * ratioy + size[1] + mesh.vertices = vert + + +if __name__ == '__main__': + from math import sin, cos + from kivy.app import App + + class TestApp(App): + + def build(self): + + graph = Graph(xlabel='Cheese', ylabel='Apples', x_ticks_minor=5, + x_ticks_major=25, y_ticks_major=1, + y_grid_label=True, x_grid_label=True, padding=5, + xlog=False, ylog=False, x_grid=True, y_grid=True, + xmin=-50, xmax=50, ymin=-1, ymax=1) + plot = MeshLinePlot(color=[1, 0, 0, 1]) + plot.points = [(x / 10., sin(x / 50.)) for x in xrange(-500, 501)] + graph.add_plot(plot) + plot = MeshLinePlot(color=[0, 1, 0, 1]) + plot.points = [(x / 10., cos(x / 50.)) for x in xrange(-600, 501)] + graph.add_plot(plot) + plot = MeshLinePlot(color=[0, 0, 1, 1]) + graph.add_plot(plot) + plot.points = [(x, x / 50.) for x in xrange(-50, 51)] + return graph + + TestApp().run() diff --git a/examples/accelerometer/libs/garden/garden.graph/graph.png b/examples/accelerometer/libs/garden/garden.graph/graph.png new file mode 100644 index 0000000000000000000000000000000000000000..544e34318a9df126101f1c3e43be3b3f65b58ef8 GIT binary patch literal 50548 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 literal 0 HcmV?d00001 diff --git a/examples/accelerometer/libs/libs/garden/garden.graph/README.md b/examples/accelerometer/libs/libs/garden/garden.graph/README.md new file mode 100644 index 000000000..3aee2fbb2 --- /dev/null +++ b/examples/accelerometer/libs/libs/garden/garden.graph/README.md @@ -0,0 +1,36 @@ +Graph +====== + +The `Graph` widget is a widget for displaying plots. It supports +drawing multiple plot with different colors on the Graph. It also supports +a title, ticks, labeled ticks, grids and a log or linear representation on +both the x and y axis, independently. + +To display a plot. First create a graph which will function as a "canvas" for +the plots. Then create plot objects e.g. MeshLinePlot and add them to the +graph. + +To create a graph with x-axis between 0-100, y-axis between -1 to 1, x and y +labels of and X and Y, respectively, x major and minor ticks every 25, 5 units, +respectively, y major ticks every 1 units, full x and y grids and with +a red line plot containing a sin wave on this range:: + + from kivy.garden.graph import Graph, MeshLinePlot + graph = Graph(xlabel='X', ylabel='Y', x_ticks_minor=5, + x_ticks_major=25, y_ticks_major=1, + y_grid_label=True, x_grid_label=True, padding=5, + x_grid=True, y_grid=True, xmin=-0, xmax=100, ymin=-1, ymax=1) + plot = MeshLinePlot(color=[1, 0, 0, 1]) + plot.points = [(x, sin(x / 10.)) for x in xrange(0, 101)] + graph.add_plot(plot) + +The `MeshLinePlot` plot is a particular plot which draws a set of points using +a mesh object. The points are given as a list of tuples, with each tuple +being a (x, y) coordinate in the graph's units. + +You can create different types of plots other than `MeshLinePlot` by inheriting +from the `Plot` class and implementing the required functions. The `Graph` object +provides a "canvas" to which a Plot's instructions are added. The plot object +is responsible for updating these instructions to show within the bounding +box of the graph the proper plot. The Graph notifies the Plot when it needs +to be redrawn due to changes. See the `MeshLinePlot` class for how it is done. diff --git a/examples/accelerometer/libs/libs/garden/garden.graph/__init__.py b/examples/accelerometer/libs/libs/garden/garden.graph/__init__.py new file mode 100644 index 000000000..dda186ee4 --- /dev/null +++ b/examples/accelerometer/libs/libs/garden/garden.graph/__init__.py @@ -0,0 +1,895 @@ +''' +Graph +====== + +The :class:`Graph` widget is a widget for displaying plots. It supports +drawing multiple plot with different colors on the Graph. It also supports +a title, ticks, labeled ticks, grids and a log or linear representation on +both the x and y axis, independently. + +To display a plot. First create a graph which will function as a "canvas" for +the plots. Then create plot objects e.g. MeshLinePlot and add them to the +graph. + +To create a graph with x-axis between 0-100, y-axis between -1 to 1, x and y +labels of and X and Y, respectively, x major and minor ticks every 25, 5 units, +respectively, y major ticks every 1 units, full x and y grids and with +a red line plot containing a sin wave on this range:: + + from kivy.garden.graph import Graph, MeshLinePlot + graph = Graph(xlabel='X', ylabel='Y', x_ticks_minor=5, + x_ticks_major=25, y_ticks_major=1, + y_grid_label=True, x_grid_label=True, padding=5, + x_grid=True, y_grid=True, xmin=-0, xmax=100, ymin=-1, ymax=1) + plot = MeshLinePlot(color=[1, 0, 0, 1]) + plot.points = [(x, sin(x / 10.)) for x in xrange(0, 101)] + graph.add_plot(plot) + +The MeshLinePlot plot is a particular plot which draws a set of points using +a mesh object. The points are given as a list of tuples, with each tuple +being a (x, y) coordinate in the graph's units. + +You can create different types of plots other than MeshLinePlot by inheriting +from the Plot class and implementing the required functions. The Graph object +provides a "canvas" to which a Plot's instructions are added. The plot object +is responsible for updating these instructions to show within the bounding +box of the graph the proper plot. The Graph notifies the Plot when it needs +to be redrawn due to changes. See the MeshLinePlot class for how it is done. + +.. note:: + + The graph uses a stencil view to clip the plots to the graph display area. + As with the stencil graphics instructions, you cannot stack more than 8 + stencil-aware widgets. + +''' + +__all__ = ('Graph', 'Plot', 'MeshLinePlot', 'MeshStemPlot') + +from math import radians +from kivy.uix.widget import Widget +from kivy.uix.label import Label +from kivy.uix.stencilview import StencilView +from kivy.properties import NumericProperty, BooleanProperty,\ + BoundedNumericProperty, StringProperty, ListProperty, ObjectProperty,\ + DictProperty, AliasProperty +from kivy.clock import Clock +from kivy.graphics import Mesh, Color +from kivy.graphics.transformation import Matrix +from kivy.event import EventDispatcher +from kivy.lang import Builder +from kivy import metrics +from math import log10, floor, ceil +from decimal import Decimal + +Builder.load_string(''' +#:kivy 1.1.0 + +: + canvas.before: + PushMatrix + MatrixInstruction: + matrix: self.transform + canvas.after: + PopMatrix + +''') + + +class RotateLabel(Label): + + transform = ObjectProperty(Matrix()) + + +class Graph(Widget): + '''Graph class, see module documentation for more information. + ''' + + # triggers a full reload of graphics + _trigger = ObjectProperty(None) + # triggers only a repositioning of objects due to size/pos updates + _trigger_size = ObjectProperty(None) + # holds widget with the x-axis label + _xlabel = ObjectProperty(None) + # holds widget with the y-axis label + _ylabel = ObjectProperty(None) + # holds all the x-axis tick mark labels + _x_grid_label = ListProperty([]) + # holds all the y-axis tick mark labels + _y_grid_label = ListProperty([]) + # holds the stencil view that clipse the plots to graph area + _plot_area = ObjectProperty(None) + # the mesh drawing all the ticks/grids + _mesh = ObjectProperty(None) + # the mesh which draws the surrounding rectangle + _mesh_rect = ObjectProperty(None) + # a list of locations of major and minor ticks. The values are not + # but is in the axis min - max range + _ticks_majorx = ListProperty([]) + _ticks_minorx = ListProperty([]) + _ticks_majory = ListProperty([]) + _ticks_minory = ListProperty([]) + + def __init__(self, **kwargs): + super(Graph, self).__init__(**kwargs) + + self._mesh = Mesh(mode='lines') + self._mesh_rect = Mesh(mode='line_strip') + val = 0.25 + self.canvas.add(Color(1 * val, 1 * val, 1 * val)) + self.canvas.add(self._mesh) + self.canvas.add(Color(1, 1, 1)) + self.canvas.add(self._mesh_rect) + mesh = self._mesh_rect + mesh.vertices = [0] * (5 * 4) + mesh.indices = [k for k in xrange(5)] + + self._plot_area = StencilView() + self.add_widget(self._plot_area) + + self._trigger = Clock.create_trigger(self._redraw_all) + self._trigger_size = Clock.create_trigger(self._redraw_size) + + self.bind(center=self._trigger_size, padding=self._trigger_size, + font_size=self._trigger_size, plots=self._trigger_size, + x_grid=self._trigger_size, y_grid=self._trigger_size, + draw_border=self._trigger_size) + self.bind(xmin=self._trigger, xmax=self._trigger, + xlog=self._trigger, x_ticks_major=self._trigger, + x_ticks_minor=self._trigger, + xlabel=self._trigger, x_grid_label=self._trigger, + ymin=self._trigger, ymax=self._trigger, + ylog=self._trigger, y_ticks_major=self._trigger, + y_ticks_minor=self._trigger, + ylabel=self._trigger, y_grid_label=self._trigger) + self._trigger() + + def _get_ticks(self, major, minor, log, s_min, s_max): + if major and s_max > s_min: + if log: + s_min = log10(s_min) + s_max = log10(s_max) + # count the decades in min - max. This is in actual decades, + # not logs. + n_decades = floor(s_max - s_min) + # for the fractional part of the last decade, we need to + # convert the log value, x, to 10**x but need to handle + # differently if the last incomplete decade has a decade + # boundary in it + if floor(s_min + n_decades) != floor(s_max): + n_decades += 1 - (10 ** (s_min + n_decades + 1) - 10 ** + s_max) / 10 ** floor(s_max + 1) + else: + n_decades += ((10 ** s_max - 10 ** (s_min + n_decades)) / + 10 ** floor(s_max + 1)) + # this might be larger than what is needed, but we delete + # excess later + n_ticks_major = n_decades / float(major) + n_ticks = int(floor(n_ticks_major * (minor if minor >= + 1. else 1.0))) + 2 + # in decade multiples, e.g. 0.1 of the decade, the distance + # between ticks + decade_dist = major / float(minor if minor else 1.0) + + points_minor = [0] * n_ticks + points_major = [0] * n_ticks + k = 0 # position in points major + k2 = 0 # position in points minor + # because each decade is missing 0.1 of the decade, if a tick + # falls in < min_pos skip it + min_pos = 0.1 - 0.00001 * decade_dist + s_min_low = floor(s_min) + # first real tick location. value is in fractions of decades + # from the start we have to use decimals here, otherwise + # floating point inaccuracies results in bad values + start_dec = ceil((10 ** Decimal(s_min - s_min_low - 1)) / + Decimal(decade_dist)) * decade_dist + count_min = (0 if not minor else + floor(start_dec / decade_dist) % minor) + start_dec += s_min_low + count = 0 # number of ticks we currently have passed start + while True: + # this is the current position in decade that we are. + # e.g. -0.9 means that we're at 0.1 of the 10**ceil(-0.9) + # decade + pos_dec = start_dec + decade_dist * count + pos_dec_low = floor(pos_dec) + diff = pos_dec - pos_dec_low + zero = abs(diff) < 0.001 * decade_dist + if zero: + # the same value as pos_dec but in log scale + pos_log = pos_dec_low + else: + pos_log = log10((pos_dec - pos_dec_low + ) * 10 ** ceil(pos_dec)) + if pos_log > s_max: + break + count += 1 + if zero or diff >= min_pos: + if minor and not count_min % minor: + points_major[k] = pos_log + k += 1 + else: + points_minor[k2] = pos_log + k2 += 1 + count_min += 1 + #n_ticks = len(points) + else: + # distance between each tick + tick_dist = major / float(minor if minor else 1.0) + n_ticks = int(floor((s_max - s_min) / tick_dist) + 1) + points_major = [0] * int(floor((s_max - s_min) / float(major)) + + 1) + points_minor = [0] * (n_ticks - len(points_major) + 1) + k = 0 # position in points major + k2 = 0 # position in points minor + for m in xrange(0, n_ticks): + if minor and m % minor: + points_minor[k2] = m * tick_dist + s_min + k2 += 1 + else: + points_major[k] = m * tick_dist + s_min + k += 1 + del points_major[k:] + del points_minor[k2:] + else: + points_major = [] + points_minor = [] + return points_major, points_minor + + def _update_labels(self): + xlabel = self._xlabel + ylabel = self._ylabel + x = self.x + y = self.y + width = self.width + height = self.height + padding = self.padding + x_next = padding + x + y_next = padding + y + xextent = x + width + yextent = y + height + ymin = self.ymin + ymax = self.ymax + xmin = self.xmin + precision = self.precision + x_overlap = False + y_overlap = False + # set up x and y axis labels + if xlabel: + xlabel.text = self.xlabel + xlabel.texture_update() + xlabel.size = xlabel.texture_size + xlabel.pos = (x + width / 2. - xlabel.width / 2., padding + y) + y_next += padding + xlabel.height + if ylabel: + ylabel.text = self.ylabel + ylabel.texture_update() + ylabel.size = ylabel.texture_size + ylabel.x = padding + x - (ylabel.width / 2. - ylabel.height / 2.) + x_next += padding + ylabel.height + xpoints = self._ticks_majorx + xlabels = self._x_grid_label + xlabel_grid = self.x_grid_label + ylabel_grid = self.y_grid_label + ypoints = self._ticks_majory + ylabels = self._y_grid_label + # now x and y tick mark labels + if len(ylabels) and ylabel_grid: + # horizontal size of the largest tick label, to have enough room + ylabels[0].text = precision % ypoints[0] + ylabels[0].texture_update() + y1 = ylabels[0].texture_size + y_start = y_next + (padding + y1[1] if len(xlabels) and xlabel_grid + else 0) +\ + (padding + y1[1] if not y_next else 0) + yextent = y + height - padding - y1[1] / 2. + if self.ylog: + ymax = log10(ymax) + ymin = log10(ymin) + ratio = (yextent - y_start) / float(ymax - ymin) + y_start -= y1[1] / 2. + func = (lambda x: 10 ** x) if self.ylog else lambda x: x + y1 = y1[0] + for k in xrange(len(ylabels)): + ylabels[k].text = precision % func(ypoints[k]) + ylabels[k].texture_update() + ylabels[k].size = ylabels[k].texture_size + y1 = max(y1, ylabels[k].texture_size[0]) + ylabels[k].pos = (x_next, y_start + (ypoints[k] - ymin) * + ratio) + if len(ylabels) > 1 and ylabels[0].top > ylabels[1].y: + y_overlap = True + else: + x_next += y1 + padding + if len(xlabels) and xlabel_grid: + func = log10 if self.xlog else lambda x: x + # find the distance from the end that'll fit the last tick label + xlabels[0].text = precision % func(xpoints[-1]) + xlabels[0].texture_update() + xextent = x + width - xlabels[0].texture_size[0] / 2. - padding + # find the distance from the start that'll fit the first tick label + if not x_next: + xlabels[0].text = precision % func(xpoints[0]) + xlabels[0].texture_update() + x_next = padding + xlabels[0].texture_size[0] / 2. + xmin = func(xmin) + ratio = (xextent - x_next) / float(func(self.xmax) - xmin) + func = (lambda x: 10 ** x) if self.xlog else lambda x: x + right = -1 + for k in xrange(len(xlabels)): + xlabels[k].text = precision % func(xpoints[k]) + # update the size so we can center the labels on ticks + xlabels[k].texture_update() + xlabels[k].size = xlabels[k].texture_size + xlabels[k].pos = (x_next + (xpoints[k] - xmin) * ratio - + xlabels[k].texture_size[0] / 2., y_next) + if xlabels[k].x < right: + x_overlap = True + break + right = xlabels[k].right + if not x_overlap: + y_next += padding + xlabels[0].texture_size[1] + # now re-center the x and y axis labels + if xlabel: + xlabel.x = x_next + (xextent - x_next) / 2. - xlabel.width / 2. + if ylabel: + ylabel.y = y_next + (yextent - y_next) / 2. - ylabel.height / 2. + t = Matrix().translate(ylabel.center[0], ylabel.center[1], 0) + t = t.multiply(Matrix().rotate(-radians(270), 0, 0, 1)) + ylabel.transform = t.multiply(Matrix().translate(-ylabel.center[0], + -ylabel.center[1], + 0)) + if x_overlap: + for k in xrange(len(xlabels)): + xlabels[k].text = '' + if y_overlap: + for k in xrange(len(ylabels)): + ylabels[k].text = '' + return x_next, y_next, xextent, yextent + + def _update_ticks(self, size): + # re-compute the positions of the bounding rectangle + mesh = self._mesh_rect + vert = mesh.vertices + if self.draw_border: + vert[0] = size[0] + vert[1] = size[1] + vert[4] = size[2] + vert[5] = size[1] + vert[8] = size[2] + vert[9] = size[3] + vert[12] = size[0] + vert[13] = size[3] + vert[16] = size[0] + vert[17] = size[1] + else: + vert[0:18] = [0 for k in xrange(18)] + mesh.vertices = vert + # re-compute the positions of the x/y axis ticks + mesh = self._mesh + vert = mesh.vertices + start = 0 + xpoints = self._ticks_majorx + ypoints = self._ticks_majory + ylog = self.ylog + xlog = self.xlog + xmin = self.xmin + xmax = self.xmax + if xlog: + xmin = log10(xmin) + xmax = log10(xmax) + ymin = self.ymin + ymax = self.ymax + if ylog: + xmin = log10(ymin) + ymax = log10(ymax) + if len(xpoints): + top = size[3] if self.x_grid else metrics.dp(12) + size[1] + ratio = (size[2] - size[0]) / float(xmax - xmin) + for k in xrange(start, len(xpoints) + start): + vert[k * 8] = size[0] + (xpoints[k - start] - xmin) * ratio + vert[k * 8 + 1] = size[1] + vert[k * 8 + 4] = vert[k * 8] + vert[k * 8 + 5] = top + start += len(xpoints) + if len(ypoints): + top = size[2] if self.y_grid else metrics.dp(12) + size[0] + ratio = (size[3] - size[1]) / float(ymax - ymin) + for k in xrange(start, len(ypoints) + start): + vert[k * 8 + 1] = size[1] + (ypoints[k - start] - ymin) * ratio + vert[k * 8 + 5] = vert[k * 8 + 1] + vert[k * 8] = size[0] + vert[k * 8 + 4] = top + mesh.vertices = vert + + def _update_plots(self, size): + ylog = self.ylog + xlog = self.xlog + xmin = self.xmin + xmax = self.xmax + ymin = self.ymin + ymax = self.ymax + for plot in self.plots: + plot._update(xlog, xmin, xmax, ylog, ymin, ymax, size) + + def _redraw_all(self, *args): + # add/remove all the required labels + font_size = self.font_size + if self.xlabel: + if not self._xlabel: + xlabel = Label(font_size=font_size) + self.add_widget(xlabel) + self._xlabel = xlabel + else: + xlabel = self._xlabel + if xlabel: + self.remove_widget(xlabel) + self._xlabel = None + grids = self._x_grid_label + xpoints_major, xpoints_minor = self._get_ticks(self.x_ticks_major, + self.x_ticks_minor, + self.xlog, self.xmin, + self.xmax) + self._ticks_majorx = xpoints_major + self._ticks_minorx = xpoints_minor + if not self.x_grid_label: + n_labels = 0 + else: + n_labels = len(xpoints_major) + for k in xrange(n_labels, len(grids)): + self.remove_widget(grids[k]) + del grids[n_labels:] + grid_len = len(grids) + grids.extend([None] * (n_labels - len(grids))) + for k in xrange(grid_len, n_labels): + grids[k] = Label(font_size=font_size) + self.add_widget(grids[k]) + + if self.ylabel: + if not self._ylabel: + ylabel = RotateLabel(font_size=font_size) + self.add_widget(ylabel) + self._ylabel = ylabel + else: + ylabel = self._ylabel + if ylabel: + self.remove_widget(ylabel) + self._ylabel = None + grids = self._y_grid_label + ypoints_major, ypoints_minor = self._get_ticks(self.y_ticks_major, + self.y_ticks_minor, + self.ylog, self.ymin, + self.ymax) + self._ticks_majory = ypoints_major + self._ticks_minory = ypoints_minor + if not self.y_grid_label: + n_labels = 0 + else: + n_labels = len(ypoints_major) + for k in xrange(n_labels, len(grids)): + self.remove_widget(grids[k]) + del grids[n_labels:] + grid_len = len(grids) + grids.extend([None] * (n_labels - len(grids))) + for k in xrange(grid_len, n_labels): + grids[k] = Label(font_size=font_size) + self.add_widget(grids[k]) + + mesh = self._mesh + n_points = (len(xpoints_major) + len(xpoints_minor) + + len(ypoints_major) + len(ypoints_minor)) + mesh.vertices = [0] * (n_points * 8) + mesh.indices = [k for k in xrange(n_points * 2)] + self._redraw_size() + + def _redraw_size(self, *args): + # size a 4-tuple describing the bounding box in which we can draw + # graphs, it's (x0, y0, x1, y1), which correspond with the bottom left + # and top right corner locations, respectively + size = self._update_labels() + self._plot_area.pos = (size[0], size[1]) + self._plot_area.size = (size[2] - size[0], size[3] - size[1]) + self._update_ticks(size) + self._update_plots(size) + + def add_plot(self, plot): + '''Add a new plot to this graph. + + :Parameters: + `plot`: + Plot to add to this graph. + + >>> graph = Graph() + >>> plot = MeshLinePlot(mode='line_strip', color=[1, 0, 0, 1]) + >>> plot.points = [(x / 10., sin(x / 50.)) for x in xrange(-0, 101)] + >>> graph.add_plot(plot) + ''' + area = self._plot_area + for group in plot._get_drawings(): + area.canvas.add(group) + self.plots = self.plots + [plot] + + def remove_plot(self, plot): + '''Remove a plot from this graph. + + :Parameters: + `plot`: + Plot to remove from this graph. + + >>> graph = Graph() + >>> plot = MeshLinePlot(mode='line_strip', color=[1, 0, 0, 1]) + >>> plot.points = [(x / 10., sin(x / 50.)) for x in xrange(-0, 101)] + >>> graph.add_plot(plot) + >>> graph.remove_plot(plot) + ''' + self._plot_area.canvas.remove_group(plot._get_group()) + self.plots.remove(plot) + + xmin = NumericProperty(0.) + '''The x-axis minimum value. + + If :data:`xlog` is True, xmin must be larger than zero. + + :data:`xmin` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. + ''' + + xmax = NumericProperty(100.) + '''The x-axis maximum value, larger than xmin. + + :data:`xmax` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. + ''' + + xlog = BooleanProperty(False) + '''Determines whether the x-axis should be displayed logarithmically (True) + or linearly (False). + + :data:`xlog` is a :class:`~kivy.properties.BooleanProperty`, defaults + to False. + ''' + + x_ticks_major = BoundedNumericProperty(0, min=0) + '''Distance between major tick marks on the x-axis. + + Determines the distance between the major tick marks. Major tick marks + start from min and re-occur at every ticks_major until :data:`xmax`. + If :data:`xmax` doesn't overlap with a integer multiple of ticks_major, + no tick will occur at :data:`xmax`. Zero indicates no tick marks. + + If :data:`xlog` is true, then this indicates the distance between ticks + in multiples of current decade. E.g. if :data:`xmin` is 0.1 and + ticks_major is 0.1, it means there will be a tick at every 10th of the + decade, i.e. 0.1 ... 0.9, 1, 2... If it is 0.3, the ticks will occur at + 0.1, 0.3, 0.6, 0.9, 2, 5, 8, 10. You'll notice that it went from 8 to 10 + instead of to 20, that's so that we can say 0.5 and have ticks at every + half decade, e.g. 0.1, 0.5, 1, 5, 10, 50... Similarly, if ticks_major is + 1.5, there will be ticks at 0.1, 5, 100, 5,000... Also notice, that there's + always a major tick at the start. Finally, if e.g. :data:`xmin` is 0.6 + and this 0.5 there will be ticks at 0.6, 1, 5... + + :data:`x_ticks_major` is a + :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. + ''' + + x_ticks_minor = BoundedNumericProperty(0, min=0) + '''The number of sub-intervals that divide x_ticks_major. + + Determines the number of sub-intervals into which ticks_major is divided, + if non-zero. The actual number of minor ticks between the major ticks is + ticks_minor - 1. Only used if ticks_major is non-zero. If there's no major + tick at xmax then the number of minor ticks after the last major + tick will be however many ticks fit until xmax. + + If self.xlog is true, then this indicates the number of intervals the + distance between major ticks is divided. The result is the number of + multiples of decades between ticks. I.e. if ticks_minor is 10, then if + ticks_major is 1, there will be ticks at 0.1, 0.2...0.9, 1, 2, 3... If + ticks_major is 0.3, ticks will occur at 0.1, 0.12, 0.15, 0.18... Finally, + as is common, if ticks major is 1, and ticks minor is 5, there will be + ticks at 0.1, 0.2, 0.4... 0.8, 1, 2... + + :data:`x_ticks_minor` is a + :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. + ''' + + x_grid = BooleanProperty(False) + '''Determines whether the x-axis has tick marks or a full grid. + + If :data:`x_ticks_major` is non-zero, then if x_grid is False tick marks + will be displayed at every major tick. If x_grid is True, instead of ticks, + a vertical line will be displayed at every major tick. + + :data:`x_grid` is a :class:`~kivy.properties.BooleanProperty`, defaults + to False. + ''' + + x_grid_label = BooleanProperty(False) + '''Whether labels should be displayed beneath each major tick. If true, + each major tick will have a label containing the axis value. + + :data:`x_grid_label` is a :class:`~kivy.properties.BooleanProperty`, + defaults to False. + ''' + + xlabel = StringProperty('') + '''The label for the x-axis. If not empty it is displayed in the center of + the axis. + + :data:`xlabel` is a :class:`~kivy.properties.StringProperty`, + defaults to ''. + ''' + + ymin = NumericProperty(0.) + '''The y-axis minimum value. + + If :data:`ylog` is True, ymin must be larger than zero. + + :data:`ymin` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. + ''' + + ymax = NumericProperty(100.) + '''The y-axis maximum value, larger than ymin. + + :data:`ymax` is a :class:`~kivy.properties.NumericProperty`, defaults to 0. + ''' + + ylog = BooleanProperty(False) + '''Determines whether the y-axis should be displayed logarithmically (True) + or linearly (False). + + :data:`ylog` is a :class:`~kivy.properties.BooleanProperty`, defaults + to False. + ''' + + y_ticks_major = BoundedNumericProperty(0, min=0) + '''Distance between major tick marks. See :data:`x_ticks_major`. + + :data:`y_ticks_major` is a + :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. + ''' + + y_ticks_minor = BoundedNumericProperty(0, min=0) + '''The number of sub-intervals that divide ticks_major. + See :data:`x_ticks_minor`. + + :data:`y_ticks_minor` is a + :class:`~kivy.properties.BoundedNumericProperty`, defaults to 0. + ''' + + y_grid = BooleanProperty(False) + '''Determines whether the y-axis has tick marks or a full grid. See + :data:`x_grid`. + + :data:`y_grid` is a :class:`~kivy.properties.BooleanProperty`, defaults + to False. + ''' + + y_grid_label = BooleanProperty(False) + '''Whether labels should be displayed beneath each major tick. If true, + each major tick will have a label containing the axis value. + + :data:`y_grid_label` is a :class:`~kivy.properties.BooleanProperty`, + defaults to False. + ''' + + ylabel = StringProperty('') + '''The label for the y-axis. If not empty it is displayed in the center of + the axis. + + :data:`ylabel` is a :class:`~kivy.properties.StringProperty`, + defaults to ''. + ''' + + padding = NumericProperty('5dp') + '''Padding distances between the labels, titles and graph, as well between + the widget and the objects near the boundaries. + + :data:`padding` is a :class:`~kivy.properties.NumericProperty`, defaults + to 5dp. + ''' + + font_size = NumericProperty('15sp') + '''Font size of the labels. + + :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, defaults + to 15sp. + ''' + + precision = StringProperty('%g') + '''Determines the numerical precision of the tick mark labels. This value + governs how the numbers are converted into string representation. Accepted + values are those listed in Python's manual in the + "String Formatting Operations" section. + + :data:`precision` is a :class:`~kivy.properties.StringProperty`, defaults + to '%g'. + ''' + + draw_border = BooleanProperty(True) + '''Whether a border is drawn around the canvas of the graph where the + plots are displayed. + + :data:`draw_border` is a :class:`~kivy.properties.BooleanProperty`, + defaults to True. + ''' + + plots = ListProperty([]) + '''Holds a list of all the plots in the graph. To add and remove plots + from the graph use :data:`add_plot` and :data:`add_plot`. Do not add + directly edit this list. + + :data:`plots` is a :class:`~kivy.properties.ListProperty`, + defaults to []. + ''' + + +class Plot(EventDispatcher): + '''Plot class, see module documentation for more information. + ''' + + # this function is called by graph whenever any of the parameters + # change. The plot should be recalculated then. + # log, min, max indicate the axis settings. + # size a 4-tuple describing the bounding box in which we can draw + # graphs, it's (x0, y0, x1, y1), which correspond with the bottom left + # and top right corner locations, respectively. + def _update(self, xlog, xmin, xmax, ylog, ymin, ymax, size): + pass + + # returns a string which is unique and is the group name given to all the + # instructions returned by _get_drawings. Graph uses this to remove + # these instructions when needed. + def _get_group(self): + return '' + + # returns a list of canvas instructions that will be added to the graph's + # canvas. These instructions must belong to a group as described + # in _get_group. + def _get_drawings(self): + return [] + + +class MeshLinePlot(Plot): + '''MeshLinePlot class which displays a set of points similar to a mesh. + ''' + + # mesh which forms the plot + _mesh = ObjectProperty(None) + # color of the plot + _color = ObjectProperty(None) + _trigger = ObjectProperty(None) + # most recent values of the params used to draw the plot + _params = DictProperty({'xlog': False, 'xmin': 0, 'xmax': 100, + 'ylog': False, 'ymin': 0, 'ymax': 100, + 'size': (0, 0, 0, 0)}) + + def __init__(self, **kwargs): + self._color = Color(1, 1, 1, group='LinePlot%d' % id(self)) + self._mesh = Mesh(mode='line_strip', group='LinePlot%d' % id(self)) + super(MeshLinePlot, self).__init__(**kwargs) + + self._trigger = Clock.create_trigger(self._redraw) + self.bind(_params=self._trigger, points=self._trigger) + + def _update(self, xlog, xmin, xmax, ylog, ymin, ymax, size): + self._params = {'xlog': xlog, 'xmin': xmin, 'xmax': xmax, 'ylog': ylog, + 'ymin': ymin, 'ymax': ymax, 'size': size} + + def _redraw(self, *args): + points = self.points + mesh = self._mesh + vert = mesh.vertices + ind = mesh.indices + params = self._params + funcx = log10 if params['xlog'] else lambda x: x + funcy = log10 if params['ylog'] else lambda x: x + xmin = funcx(params['xmin']) + ymin = funcy(params['ymin']) + diff = len(points) - len(vert) / 4 + size = params['size'] + ratiox = (size[2] - size[0]) / float(funcx(params['xmax']) - xmin) + ratioy = (size[3] - size[1]) / float(funcy(params['ymax']) - ymin) + if diff < 0: + del vert[4 * len(points):] + del ind[len(points):] + elif diff > 0: + ind.extend(xrange(len(ind), len(ind) + diff)) + vert.extend([0] * (diff * 4)) + for k in xrange(len(points)): + vert[k * 4] = (funcx(points[k][0]) - xmin) * ratiox + size[0] + vert[k * 4 + 1] = (funcy(points[k][1]) - ymin) * ratioy + size[1] + mesh.vertices = vert + + def _get_group(self): + return 'LinePlot%d' % id(self) + + def _get_drawings(self): + return [self._color, self._mesh] + + def _set_mode(self, value): + self._mesh.mode = value + mode = AliasProperty(lambda self: self._mesh.mode, _set_mode) + '''VBO Mode used for drawing the points. Can be one of: 'points', + 'line_strip', 'line_loop', 'lines', 'triangle_strip', 'triangle_fan'. + See :class:`~kivy.graphics.Mesh` for more details. + + Defaults to 'line_strip'. + ''' + + def _set_color(self, value): + self._color.rgba = value + color = AliasProperty(lambda self: self._color.rgba, _set_color) + '''Plot color, in the format [r, g, b, a] with values between 0-1. + + Defaults to [1, 1, 1, 1]. + ''' + + points = ListProperty([]) + '''List of x, y points to be displayed in the plot. + + The elements of points are 2-tuples, (x, y). The points are displayed + based on the mode setting. + + :data:`points` is a :class:`~kivy.properties.ListProperty`, defaults to + []. + ''' + +class MeshStemPlot(MeshLinePlot): + '''MeshStemPlot uses the MeshLinePlot class to draw a stem plot. The data + provided is graphed from origin to the data point. + ''' + + def _redraw(self, *args): + points = self.points + mesh = self._mesh + self._mesh.mode = 'lines' + vert = mesh.vertices + ind = mesh.indices + params = self._params + funcx = log10 if params['xlog'] else lambda x: x + funcy = log10 if params['ylog'] else lambda x: x + xmin = funcx(params['xmin']) + ymin = funcy(params['ymin']) + diff = len(points) * 2 - len(vert) / 4 + size = params['size'] + ratiox = (size[2] - size[0]) / float(funcx(params['xmax']) - xmin) + ratioy = (size[3] - size[1]) / float(funcy(params['ymax']) - ymin) + if diff < 0: + del vert[4 * len(points):] + del ind[len(points):] + elif diff > 0: + ind.extend(xrange(len(ind), len(ind) + diff)) + vert.extend([0] * (diff * 4)) + for k in xrange(len(points)): + vert[k * 8] = (funcx(points[k][0]) - xmin) * ratiox + size[0] + vert[k * 8 + 1] = (0 - ymin) * ratioy + size[1] + vert[k * 8 + 4] = (funcx(points[k][0]) - xmin) * ratiox + size[0] + vert[k * 8 + 5] = (funcy(points[k][1]) - ymin) * ratioy + size[1] + mesh.vertices = vert + + +if __name__ == '__main__': + from math import sin, cos + from kivy.app import App + + class TestApp(App): + + def build(self): + + graph = Graph(xlabel='Cheese', ylabel='Apples', x_ticks_minor=5, + x_ticks_major=25, y_ticks_major=1, + y_grid_label=True, x_grid_label=True, padding=5, + xlog=False, ylog=False, x_grid=True, y_grid=True, + xmin=-50, xmax=50, ymin=-1, ymax=1) + plot = MeshLinePlot(color=[1, 0, 0, 1]) + plot.points = [(x / 10., sin(x / 50.)) for x in xrange(-500, 501)] + graph.add_plot(plot) + plot = MeshLinePlot(color=[0, 1, 0, 1]) + plot.points = [(x / 10., cos(x / 50.)) for x in xrange(-600, 501)] + graph.add_plot(plot) + plot = MeshLinePlot(color=[0, 0, 1, 1]) + graph.add_plot(plot) + plot.points = [(x, x / 50.) for x in xrange(-50, 51)] + return graph + + TestApp().run() diff --git a/examples/accelerometer/libs/libs/garden/garden.graph/graph.png b/examples/accelerometer/libs/libs/garden/garden.graph/graph.png new file mode 100644 index 0000000000000000000000000000000000000000..544e34318a9df126101f1c3e43be3b3f65b58ef8 GIT binary patch literal 50548 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 literal 0 HcmV?d00001 diff --git a/examples/accelerometer/main.py b/examples/accelerometer/main.py new file mode 100644 index 000000000..edf3d1396 --- /dev/null +++ b/examples/accelerometer/main.py @@ -0,0 +1,89 @@ +import kivy +kivy.require('1.8.0') + +from kivy.app import App +from kivy.properties import ObjectProperty +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.popup import Popup +from kivy.clock import Clock + +from plyer import accelerometer + +''' +This example uses Kivy Garden Graph addon to draw graphs plotting the +accelerometer values in X,Y and Z axes. +The package is installed in the directory: ./libs/garden/garden.graph +To read more about kivy garden, visit: http://kivy-garden.github.io/. +''' +from kivy.garden.graph import Graph, MeshLinePlot +# from collections import deque + +class AccelerometerDemo(BoxLayout): + def __init__(self): + super(AccelerometerDemo, self).__init__() + + self.sensorEnabled = False + self.graph = self.ids.graph_plot + + # For all X, Y and Z axes + self.plot = [] + self.plot.append(MeshLinePlot(color=[1, 0, 0, 1])) #X - Red + self.plot.append(MeshLinePlot(color=[0, 1, 0, 1])) #Y - Green + self.plot.append(MeshLinePlot(color=[0, 0, 1, 1])) #Z - Blue + + self.reset_plots() + + for plot in self.plot: + self.graph.add_plot(plot) + + def reset_plots(self): + for plot in self.plot: + plot.points = [(0,0)] + + self.counter = 1 + + def do_toggle(self): + try: + if not self.sensorEnabled: + self.sensorEnabled = True + accelerometer.enable() + Clock.schedule_interval(self.get_acceleration, 1 / 20.) + + self.ids.toggle_button.text = "Stop Accelerometer" + else: + accelerometer.disable() + self.reset_plots() + Clock.unschedule(self.get_acceleration) + self.sensorEnabled = False + + self.ids.toggle_button.text = "Start Accelerometer" + except NotImplementedError: + popup = ErrorPopup() + popup.open() + + def get_acceleration(self, dt): + if (self.counter == 100): + # We re-write our points list if number of values exceed 100. ie. Move each timestamp to the left. + for plot in self.plot: + del(plot.points[0]) + plot.points[:] = [(i[0]-1, i[1]) for i in plot.points[:]] + + self.counter = 99 + + val = accelerometer.acceleration + + self.plot[0].points.append((self.counter, val[0])) + self.plot[1].points.append((self.counter, val[1])) + self.plot[2].points.append((self.counter, val[2])) + + self.counter += 1 + +class AccelerometerDemoApp(App): + def build(self): + return AccelerometerDemo() + +class ErrorPopup(Popup): + pass +if __name__ == '__main__': + AccelerometerDemoApp().run() + \ No newline at end of file diff --git a/examples/accelerometer/test.py b/examples/accelerometer/test.py new file mode 100644 index 000000000..dbf5f31c3 --- /dev/null +++ b/examples/accelerometer/test.py @@ -0,0 +1,26 @@ +from math import sin, cos +from kivy.app import App + +from kivy.garden.graph import Graph, MeshLinePlot + +class TestApp(App): + + def build(self): + + graph = Graph(xlabel='Cheese', ylabel='Apples', x_ticks_minor=5, + x_ticks_major=25, y_ticks_major=1, + y_grid_label=True, x_grid_label=True, padding=5, + xlog=False, ylog=False, x_grid=True, y_grid=True, + xmin=-50, xmax=50, ymin=-1, ymax=1) + plot = MeshLinePlot(color=[1, 0, 0, 1]) + plot.points = [(x / 10., sin(x / 50.)) for x in xrange(-500, 501)] + graph.add_plot(plot) + plot = MeshLinePlot(color=[0, 1, 0, 1]) + plot.points = [(x / 10., cos(x / 50.)) for x in xrange(-600, 501)] + graph.add_plot(plot) + plot = MeshLinePlot(color=[0, 0, 1, 1]) + graph.add_plot(plot) + plot.points = [(x, x / 50.) for x in xrange(-50, 51)] + return graph + +TestApp().run() \ No newline at end of file From 5474855df038fab7229be2f0ce7067f063dc05a8 Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Wed, 12 Mar 2014 19:01:53 -0400 Subject: [PATCH 02/14] Removed a test file not a part of the example. --- examples/accelerometer/test.py | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 examples/accelerometer/test.py diff --git a/examples/accelerometer/test.py b/examples/accelerometer/test.py deleted file mode 100644 index dbf5f31c3..000000000 --- a/examples/accelerometer/test.py +++ /dev/null @@ -1,26 +0,0 @@ -from math import sin, cos -from kivy.app import App - -from kivy.garden.graph import Graph, MeshLinePlot - -class TestApp(App): - - def build(self): - - graph = Graph(xlabel='Cheese', ylabel='Apples', x_ticks_minor=5, - x_ticks_major=25, y_ticks_major=1, - y_grid_label=True, x_grid_label=True, padding=5, - xlog=False, ylog=False, x_grid=True, y_grid=True, - xmin=-50, xmax=50, ymin=-1, ymax=1) - plot = MeshLinePlot(color=[1, 0, 0, 1]) - plot.points = [(x / 10., sin(x / 50.)) for x in xrange(-500, 501)] - graph.add_plot(plot) - plot = MeshLinePlot(color=[0, 1, 0, 1]) - plot.points = [(x / 10., cos(x / 50.)) for x in xrange(-600, 501)] - graph.add_plot(plot) - plot = MeshLinePlot(color=[0, 0, 1, 1]) - graph.add_plot(plot) - plot.points = [(x, x / 50.) for x in xrange(-50, 51)] - return graph - -TestApp().run() \ No newline at end of file From 50dc04b5f868765c71882903273eaac7e939721e Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Wed, 12 Mar 2014 19:24:59 -0400 Subject: [PATCH 03/14] Changed popup error message to state that the problem is with the current platform. --- examples/accelerometer/accelerometerdemo.kv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/accelerometer/accelerometerdemo.kv b/examples/accelerometer/accelerometerdemo.kv index daa07e554..43335a5af 100644 --- a/examples/accelerometer/accelerometerdemo.kv +++ b/examples/accelerometer/accelerometerdemo.kv @@ -34,7 +34,7 @@ Label: size_hint_y: 0.4 - text: "This feature has not yet been implemented in Plyer." + text: "This feature has not yet been implemented on this platform." Button: text: 'Dismiss' size_hint_y: 0.4 From 4dc2773f9c2804598b565b99f69cf105c5926900 Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Wed, 12 Mar 2014 19:31:36 -0400 Subject: [PATCH 04/14] Fixed android permissions. Doesn't require any --- examples/accelerometer/buildozer.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/accelerometer/buildozer.spec b/examples/accelerometer/buildozer.spec index b7f6e9d2b..a05b4e6c0 100644 --- a/examples/accelerometer/buildozer.spec +++ b/examples/accelerometer/buildozer.spec @@ -52,7 +52,7 @@ fullscreen = 0 # # (list) Permissions -android.permissions = android.hardware.sensor.accelerometer +# android.permissions = android.hardware.sensor.accelerometer # (int) Android API to use #android.api = 14 From 52163c3771965914b54e539ee26c39d91559b894 Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Wed, 12 Mar 2014 22:06:39 -0400 Subject: [PATCH 05/14] Fixed ordering in the try catch block. It fails at the correct line in the try block if the accelerometer is not present --- examples/accelerometer/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/accelerometer/main.py b/examples/accelerometer/main.py index edf3d1396..959cb81fb 100644 --- a/examples/accelerometer/main.py +++ b/examples/accelerometer/main.py @@ -45,17 +45,17 @@ def reset_plots(self): def do_toggle(self): try: if not self.sensorEnabled: - self.sensorEnabled = True accelerometer.enable() Clock.schedule_interval(self.get_acceleration, 1 / 20.) + self.sensorEnabled = True self.ids.toggle_button.text = "Stop Accelerometer" else: accelerometer.disable() self.reset_plots() Clock.unschedule(self.get_acceleration) + self.sensorEnabled = False - self.ids.toggle_button.text = "Start Accelerometer" except NotImplementedError: popup = ErrorPopup() From f7663b2bdf41b6dedecd221a4695ddd2c89751de Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Wed, 12 Mar 2014 22:10:05 -0400 Subject: [PATCH 06/14] Cleaned up a bit. Newlines. Removed dangling commented lines. --- examples/accelerometer/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/accelerometer/main.py b/examples/accelerometer/main.py index 959cb81fb..fc8d494ff 100644 --- a/examples/accelerometer/main.py +++ b/examples/accelerometer/main.py @@ -16,7 +16,6 @@ To read more about kivy garden, visit: http://kivy-garden.github.io/. ''' from kivy.garden.graph import Graph, MeshLinePlot -# from collections import deque class AccelerometerDemo(BoxLayout): def __init__(self): @@ -84,6 +83,7 @@ def build(self): class ErrorPopup(Popup): pass + if __name__ == '__main__': AccelerometerDemoApp().run() \ No newline at end of file From d8b61f31506ee968e72e569fb342bb27a68cce6b Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Thu, 13 Mar 2014 07:00:42 -0400 Subject: [PATCH 07/14] Added README on how to build --- examples/accelerometer/README | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 examples/accelerometer/README diff --git a/examples/accelerometer/README b/examples/accelerometer/README new file mode 100644 index 000000000..d9db7621e --- /dev/null +++ b/examples/accelerometer/README @@ -0,0 +1,15 @@ +Python-for-android compilation: + + ./distribute.sh -m 'kivy plyer' + cd dist/default + ./build.py --org.test.accelexample --name "Kivy Accelerometer" \ + --dir /path/to/plyer/examples/accelerometer --version 1.0 \ + debug installd + + +Buildozer: + + cd /path/to/plyer/examples/accelerometer + buildozer init + # edit the buildozer.spec, then + buildozer android debug deploy run From 1c93a1d066e7c392157d6146cdf3fee21dd749ad Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Thu, 13 Mar 2014 07:01:08 -0400 Subject: [PATCH 08/14] Added screenshot of the app running on Android 4.3 --- .../accelerometer/Screenshot_Android_4.3.png | Bin 0 -> 35315 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/accelerometer/Screenshot_Android_4.3.png diff --git a/examples/accelerometer/Screenshot_Android_4.3.png b/examples/accelerometer/Screenshot_Android_4.3.png new file mode 100644 index 0000000000000000000000000000000000000000..68fe038ddc0d42d2562e61ed1e5904e4e3f0a530 GIT binary patch literal 35315 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 literal 0 HcmV?d00001 From 0da3683553e8d79be95aa186d8c539108335763c Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Thu, 13 Mar 2014 07:37:28 -0400 Subject: [PATCH 09/14] Updated README to include information about garden.graph --- examples/accelerometer/README | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/accelerometer/README b/examples/accelerometer/README index d9db7621e..51077d57e 100644 --- a/examples/accelerometer/README +++ b/examples/accelerometer/README @@ -13,3 +13,8 @@ Buildozer: buildozer init # edit the buildozer.spec, then buildozer android debug deploy run + + +This example uses Garden Graph widget by matham. Available at https://github.com/kivy-garden/garden.graph. The required files are present it ./libs/garden/garden.graph. If you need to update these files, use: + cd /path/to/plyer/examples/accelerometer + /path/to/kivy_instalation/kivy/tools/garden install --app graph From ad8bc97aa121dad0563a7234aa00d92ed61325fc Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Thu, 13 Mar 2014 08:34:59 -0400 Subject: [PATCH 10/14] Moved the graph example to examples/accelerometer/using_graph --- examples/accelerometer/{ => using_graph}/README | 0 .../{ => using_graph}/Screenshot_Android_4.3.png | Bin .../{ => using_graph}/accelerometerdemo.kv | 0 .../accelerometer/{ => using_graph}/buildozer.spec | 0 .../libs/garden/garden.graph/README.md | 0 .../libs/garden/garden.graph/__init__.py | 0 .../libs/garden/garden.graph/graph.png | Bin .../libs/libs/garden/garden.graph/README.md | 0 .../libs/libs/garden/garden.graph/__init__.py | 0 .../libs/libs/garden/garden.graph/graph.png | Bin examples/accelerometer/{ => using_graph}/main.py | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename examples/accelerometer/{ => using_graph}/README (100%) rename examples/accelerometer/{ => using_graph}/Screenshot_Android_4.3.png (100%) rename examples/accelerometer/{ => using_graph}/accelerometerdemo.kv (100%) rename examples/accelerometer/{ => using_graph}/buildozer.spec (100%) rename examples/accelerometer/{ => using_graph}/libs/garden/garden.graph/README.md (100%) rename examples/accelerometer/{ => using_graph}/libs/garden/garden.graph/__init__.py (100%) rename examples/accelerometer/{ => using_graph}/libs/garden/garden.graph/graph.png (100%) rename examples/accelerometer/{ => using_graph}/libs/libs/garden/garden.graph/README.md (100%) rename examples/accelerometer/{ => using_graph}/libs/libs/garden/garden.graph/__init__.py (100%) rename examples/accelerometer/{ => using_graph}/libs/libs/garden/garden.graph/graph.png (100%) rename examples/accelerometer/{ => using_graph}/main.py (100%) diff --git a/examples/accelerometer/README b/examples/accelerometer/using_graph/README similarity index 100% rename from examples/accelerometer/README rename to examples/accelerometer/using_graph/README diff --git a/examples/accelerometer/Screenshot_Android_4.3.png b/examples/accelerometer/using_graph/Screenshot_Android_4.3.png similarity index 100% rename from examples/accelerometer/Screenshot_Android_4.3.png rename to examples/accelerometer/using_graph/Screenshot_Android_4.3.png diff --git a/examples/accelerometer/accelerometerdemo.kv b/examples/accelerometer/using_graph/accelerometerdemo.kv similarity index 100% rename from examples/accelerometer/accelerometerdemo.kv rename to examples/accelerometer/using_graph/accelerometerdemo.kv diff --git a/examples/accelerometer/buildozer.spec b/examples/accelerometer/using_graph/buildozer.spec similarity index 100% rename from examples/accelerometer/buildozer.spec rename to examples/accelerometer/using_graph/buildozer.spec diff --git a/examples/accelerometer/libs/garden/garden.graph/README.md b/examples/accelerometer/using_graph/libs/garden/garden.graph/README.md similarity index 100% rename from examples/accelerometer/libs/garden/garden.graph/README.md rename to examples/accelerometer/using_graph/libs/garden/garden.graph/README.md diff --git a/examples/accelerometer/libs/garden/garden.graph/__init__.py b/examples/accelerometer/using_graph/libs/garden/garden.graph/__init__.py similarity index 100% rename from examples/accelerometer/libs/garden/garden.graph/__init__.py rename to examples/accelerometer/using_graph/libs/garden/garden.graph/__init__.py diff --git a/examples/accelerometer/libs/garden/garden.graph/graph.png b/examples/accelerometer/using_graph/libs/garden/garden.graph/graph.png similarity index 100% rename from examples/accelerometer/libs/garden/garden.graph/graph.png rename to examples/accelerometer/using_graph/libs/garden/garden.graph/graph.png diff --git a/examples/accelerometer/libs/libs/garden/garden.graph/README.md b/examples/accelerometer/using_graph/libs/libs/garden/garden.graph/README.md similarity index 100% rename from examples/accelerometer/libs/libs/garden/garden.graph/README.md rename to examples/accelerometer/using_graph/libs/libs/garden/garden.graph/README.md diff --git a/examples/accelerometer/libs/libs/garden/garden.graph/__init__.py b/examples/accelerometer/using_graph/libs/libs/garden/garden.graph/__init__.py similarity index 100% rename from examples/accelerometer/libs/libs/garden/garden.graph/__init__.py rename to examples/accelerometer/using_graph/libs/libs/garden/garden.graph/__init__.py diff --git a/examples/accelerometer/libs/libs/garden/garden.graph/graph.png b/examples/accelerometer/using_graph/libs/libs/garden/garden.graph/graph.png similarity index 100% rename from examples/accelerometer/libs/libs/garden/garden.graph/graph.png rename to examples/accelerometer/using_graph/libs/libs/garden/garden.graph/graph.png diff --git a/examples/accelerometer/main.py b/examples/accelerometer/using_graph/main.py similarity index 100% rename from examples/accelerometer/main.py rename to examples/accelerometer/using_graph/main.py From 8ff33d43289c4264ea8790547e0a804abdd94e0f Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Thu, 13 Mar 2014 08:38:25 -0400 Subject: [PATCH 11/14] Updated README to reflect path changes --- examples/accelerometer/using_graph/README | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/accelerometer/using_graph/README b/examples/accelerometer/using_graph/README index 51077d57e..b36f180da 100644 --- a/examples/accelerometer/using_graph/README +++ b/examples/accelerometer/using_graph/README @@ -3,18 +3,18 @@ Python-for-android compilation: ./distribute.sh -m 'kivy plyer' cd dist/default ./build.py --org.test.accelexample --name "Kivy Accelerometer" \ - --dir /path/to/plyer/examples/accelerometer --version 1.0 \ + --dir /path/to/plyer/examples/accelerometer/using_graph --version 1.0 \ debug installd Buildozer: - cd /path/to/plyer/examples/accelerometer + cd /path/to/plyer/examples/accelerometer/using_graph buildozer init # edit the buildozer.spec, then buildozer android debug deploy run This example uses Garden Graph widget by matham. Available at https://github.com/kivy-garden/garden.graph. The required files are present it ./libs/garden/garden.graph. If you need to update these files, use: - cd /path/to/plyer/examples/accelerometer + cd /path/to/plyer/examples/accelerometer/using_graph /path/to/kivy_instalation/kivy/tools/garden install --app graph From 99fc193dcde3aa3eb70d5069044160f767db094b Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Thu, 13 Mar 2014 09:04:54 -0400 Subject: [PATCH 12/14] Corrected README for buildozer commands --- examples/accelerometer/using_graph/README | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/accelerometer/using_graph/README b/examples/accelerometer/using_graph/README index b36f180da..2a163cb1c 100644 --- a/examples/accelerometer/using_graph/README +++ b/examples/accelerometer/using_graph/README @@ -10,8 +10,7 @@ Python-for-android compilation: Buildozer: cd /path/to/plyer/examples/accelerometer/using_graph - buildozer init - # edit the buildozer.spec, then + # edit the buildozer.spec if required, then buildozer android debug deploy run From 10e2d728538d4d87aba49eef90ff13acdf5c20fd Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Thu, 13 Mar 2014 09:45:41 -0400 Subject: [PATCH 13/14] Added a simple accelerometer example --- .../basic/.buildozer/state.db.db | Bin 0 -> 16384 bytes examples/accelerometer/basic/README | 17 ++ .../accelerometer/basic/accelerometertest.kv | 30 +++ examples/accelerometer/basic/buildozer.spec | 173 ++++++++++++++++++ examples/accelerometer/basic/main.py | 49 +++++ 5 files changed, 269 insertions(+) create mode 100644 examples/accelerometer/basic/.buildozer/state.db.db create mode 100644 examples/accelerometer/basic/README create mode 100644 examples/accelerometer/basic/accelerometertest.kv create mode 100644 examples/accelerometer/basic/buildozer.spec create mode 100644 examples/accelerometer/basic/main.py diff --git a/examples/accelerometer/basic/.buildozer/state.db.db b/examples/accelerometer/basic/.buildozer/state.db.db new file mode 100644 index 0000000000000000000000000000000000000000..7e43911e420b1f7faf82fd0a048b5e771313e274 GIT binary patch literal 16384 zcmeI1!EPHj5QbH^D1x9sdg(JHr$Ur1+v<(lr6g9n%LU1m;#>$9$-!uv6fuxnfxJYY zs<#3?_mJl(+L?b=tBo7TPjBgW{_x-Y^T9v&5b6g%HXoP?WCEE$CXfka0+~Q2 zkO^c0nLs9x34AmJPA0!U`0c?j^z+e#oCD1SGJ#AW6UYQIflMG1$OJNhOdu1;1Tq08 z5FXCd!}~v~`(Izbc>3z)&C_qLUtixmzy9v|v-IP$S3lg`-rl@?ahqytPC%KzNw>-xVFc=VjoG(6NrVM0j7#uQuIq}IFE_-IX_G@jkRJ}z+D2a_sS=xAL- z-qVqHWt(?=XCvR?kz^i6uT$abS~n%{KOe2CU9lbSem?RwChnczrVw>B^xVbB7dpSv zg-NT*Hf!U_dpYv%?aB_1EqaIRnwTgCE$h`3_OeXjDnwJKVEoP&e2`KH*Upip*0y4+ zaOvX4G|>_dlZYbk%AypwEOm^=U*T+6L1by0lGhS(yR?;!wBUFcaLAIj-P>|)BJT$h zFqekoO1i8qUw{G!cx=;lq{+mME7=1E@OV?ZNY9x)!T#Axsj;PiZiyQrzvSnqnDMTMClXp6q1K!&C0GjKhy_w_s+X1dIg{!!!^;nYN)i-wd6;&p8-v2Dnkj49n*^V zF#I#n>0SMe!46D)TDESF%s2y-(&}~N0!iZMfugj5@ujV~T6fy>U7rC%+0`xo0lnsN zOYH#=x{#KdlkE(8sm`OLnxb&IFwP)$Rq3#)?0N$eG_IVdDp^uu(W*IAtz6W-RJK75 zmDjj#=g`8&LrMnwgrUx%haFLeEz6!m&bo3-T~VcIUvRd~p=CqD_S&06lyiu(&9|nY z{Mk_!VFw7Z4P7WD=g=Xk`|fNBigTzttQaw$L!)h|y)=Bh^sGH~7PqG;&Y@S_=pW}0 zyR~!|aQNo{P*bu}ej(l12>rDy?dmG& zuC$KW#7J!!kAOq(MKun2ehwHl+A`f;t)nC!l;K&hkOf`SFXYrbt^kx~#mRO70@S5~ zrTD07DQ&3IBR7i+!05#%*TuD?_$b?}iVwN~32$`CCexJ2z6)?TSoylvycP_^N<~G7 zUFVbc;uRUrGS4}a# z^~i1 z*hIjhmnpW^^&jLCFWlZW%gFwpvA^Xx^ zN9xkt&K3{|XQe$OTtK5(_WCMN0W=oxFg1lfxEf2fL6}nD?4-xk6hhfqnq{}vOx*mAQ!}Ii!lFCl z>22t{N(MFYjhdR0ac!_4+M1Gat+yW=o07pz?C)u9O2#(vuluPMiez#V$NPGlk_k>0 zywkGO4BEwW>`h9BI57uPbo90zE0$UA;69R&p{}>1PbeASdefb@rcQDDz4K0EQ!>Db v^Y1N8ol+(I^S=T7@n`;I0+~Q2kO^c0nLs9x31kA9Kqin0WCEGMf1SWz>|3(% literal 0 HcmV?d00001 diff --git a/examples/accelerometer/basic/README b/examples/accelerometer/basic/README new file mode 100644 index 000000000..2dcff31c7 --- /dev/null +++ b/examples/accelerometer/basic/README @@ -0,0 +1,17 @@ +Basic acccelerometer example. + + +Python-for-android compilation: + + ./distribute.sh -m 'kivy plyer' + cd dist/default + ./build.py --org.test.accelsimpleexample --name "Kivy Basic Accelerometer" \ + --dir /path/to/plyer/examples/accelerometer/basic --version 1.0 \ + debug installd + + +Buildozer: + + cd /path/to/plyer/examples/accelerometer/basic + # edit the buildozer.spec if required, then + buildozer android debug deploy run diff --git a/examples/accelerometer/basic/accelerometertest.kv b/examples/accelerometer/basic/accelerometertest.kv new file mode 100644 index 000000000..b811f6fc0 --- /dev/null +++ b/examples/accelerometer/basic/accelerometertest.kv @@ -0,0 +1,30 @@ +#:kivy 1.8.0 +: + BoxLayout: + orientation: 'vertical' + + Label: + id: x_label + text: 'X: ' + + Label: + id: y_label + text: 'Y: ' + + Label: + id: z_label + text: 'Z: ' + + Label: + id: accel_status + text: '' + + BoxLayout: + size_hint_y: None + height: '48dp' + padding: '4dp' + + ToggleButton: + id: toggle_button + text: 'Start accelerometer' + on_press: root.do_toggle() \ No newline at end of file diff --git a/examples/accelerometer/basic/buildozer.spec b/examples/accelerometer/basic/buildozer.spec new file mode 100644 index 000000000..b438c8b2b --- /dev/null +++ b/examples/accelerometer/basic/buildozer.spec @@ -0,0 +1,173 @@ +[app] + +# (str) Title of your application +title = Kivy Basic Accelerometer + +# (str) Package name +package.name = accelbasicexample + +# (str) Package domain (needed for android/ios packaging) +package.domain = org.test + +# (str) Source code where the main.py live +source.dir = . + +# (list) Source files to include (let empty to include all the files) +source.include_exts = py,png,jpg,kv,atlas + +# (list) Source files to exclude (let empty to not exclude anything) +#source.exclude_exts = spec + +# (list) List of directory to exclude (let empty to not exclude anything) +#source.exclude_dirs = tests, bin + +# (list) List of exclusions using pattern matching +#source.exclude_patterns = license,images/*/*.jpg + +# (str) Application versioning (method 1) +# version.regex = __version__ = '(.*)' +# version.filename = %(source.dir)s/main.py + +# (str) Application versioning (method 2) +version = 1.0 + +# (list) Application requirements +requirements = plyer,kivy + +# (str) Presplash of the application +#presplash.filename = %(source.dir)s/data/presplash.png + +# (str) Icon of the application +#icon.filename = %(source.dir)s/data/icon.png + +# (str) Supported orientation (one of landscape, portrait or all) +orientation = portrait + +# (bool) Indicate if the application should be fullscreen or not +fullscreen = 0 + + +# +# Android specific +# + +# (list) Permissions +# android.permissions = android.hardware.sensor.accelerometer + +# (int) Android API to use +#android.api = 14 + +# (int) Minimum API required (8 = Android 2.2 devices) +#android.minapi = 8 + +# (int) Android SDK version to use +#android.sdk = 21 + +# (str) Android NDK version to use +#android.ndk = 9 + +# (bool) Use --private data storage (True) or --dir public storage (False) +#android.private_storage = True + +# (str) Android NDK directory (if empty, it will be automatically downloaded.) +#android.ndk_path = + +# (str) Android SDK directory (if empty, it will be automatically downloaded.) +#android.sdk_path = + +# (str) Android entry point, default is ok for Kivy-based app +#android.entrypoint = org.renpy.android.PythonActivity + +# (list) List of Java .jar files to add to the libs so that pyjnius can access +# their classes. Don't add jars that you do not need, since extra jars can slow +# down the build process. Allows wildcards matching, for example: +# OUYA-ODK/libs/*.jar +#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar + +# (list) List of Java files to add to the android project (can be java or a +# directory containing the files) +#android.add_src = + +# (str) python-for-android branch to use, if not master, useful to try +# not yet merged features. +#android.branch = master + +# (str) OUYA Console category. Should be one of GAME or APP +# If you leave this blank, OUYA support will not be enabled +#android.ouya.category = GAME + +# (str) Filename of OUYA Console icon. It must be a 732x412 png image. +#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png + +# (str) XML file to include as an intent filters in tag +#android.manifest.intent_filters = + +# (list) Android additionnal libraries to copy into libs/armeabi +#android.add_libs_armeabi = libs/android/*.so + +# (bool) Indicate whether the screen should stay on +# Don't forget to add the WAKE_LOCK permission if you set this to True +#android.wakelock = False + +# (list) Android application meta-data to set (key=value format) +#android.meta_data = + +# (list) Android library project to add (will be added in the +# project.properties automatically.) +#android.library_references = + +# +# iOS specific +# + +# (str) Name of the certificate to use for signing the debug version +# Get a list of available identities: buildozer ios list_identities +#ios.codesign.debug = "iPhone Developer: ()" + +# (str) Name of the certificate to use for signing the release version +#ios.codesign.release = %(ios.codesign.debug)s + + +[buildozer] + +# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) +log_level = 2 + + +# ----------------------------------------------------------------------------- +# List as sections +# +# You can define all the "list" as [section:key]. +# Each line will be considered as a option to the list. +# Let's take [app] / source.exclude_patterns. +# Instead of doing: +# +# [app] +# source.exclude_patterns = license,data/audio/*.wav,data/images/original/* +# +# This can be translated into: +# +# [app:source.exclude_patterns] +# license +# data/audio/*.wav +# data/images/original/* +# + + +# ----------------------------------------------------------------------------- +# Profiles +# +# You can extend section / key with a profile +# For example, you want to deploy a demo version of your application without +# HD content. You could first change the title to add "(demo)" in the name +# and extend the excluded directories to remove the HD content. +# +# [app@demo] +# title = My Application (demo) +# +# [app:source.exclude_patterns@demo] +# images/hd/* +# +# Then, invoke the command line with the "demo" profile: +# +# buildozer --profile demo android debug diff --git a/examples/accelerometer/basic/main.py b/examples/accelerometer/basic/main.py new file mode 100644 index 000000000..bc4338d0a --- /dev/null +++ b/examples/accelerometer/basic/main.py @@ -0,0 +1,49 @@ +''' +Basic accelerometer example. +''' + +from kivy.lang import Builder +from kivy.app import App +from kivy.properties import ObjectProperty +from kivy.uix.boxlayout import BoxLayout +from kivy.clock import Clock + +from plyer import accelerometer + +class AccelerometerTest(BoxLayout): + def __init__(self): + super(AccelerometerTest, self).__init__() + self.accel_status = "" + self.sensorEnabled = False + + def do_toggle(self): + try: + if not self.sensorEnabled: + accelerometer.enable() + Clock.schedule_interval(self.get_acceleration, 1 / 20.) + + self.sensorEnabled = True + self.ids.toggle_button.text = "Stop Accelerometer" + else: + accelerometer.disable() + Clock.unschedule(self.get_acceleration) + + self.sensorEnabled = False + self.ids.toggle_button.text = "Start Accelerometer" + except NotImplementedError: + import traceback; traceback.print_exc() + self.ids.accel_status.text = "Accelerometer is not implemented for your platform" + + def get_acceleration(self, dt): + val = accelerometer.acceleration + + self.ids.x_label.text = "X: " + str(val[0]) + self.ids.y_label.text = "Y: " + str(val[1]) + self.ids.z_label.text = "Z: " + str(val[2]) + +class AccelerometerTestApp(App): + def build(self): + return AccelerometerTest() + +if __name__ == '__main__': + AccelerometerTestApp().run() From eaea99bb60ed914f9247cb284683d285dab5bedd Mon Sep 17 00:00:00 2001 From: gtrivedi Date: Thu, 13 Mar 2014 09:49:52 -0400 Subject: [PATCH 14/14] Removed unused variable --- examples/accelerometer/basic/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/accelerometer/basic/main.py b/examples/accelerometer/basic/main.py index bc4338d0a..a8192e03f 100644 --- a/examples/accelerometer/basic/main.py +++ b/examples/accelerometer/basic/main.py @@ -13,7 +13,6 @@ class AccelerometerTest(BoxLayout): def __init__(self): super(AccelerometerTest, self).__init__() - self.accel_status = "" self.sensorEnabled = False def do_toggle(self):