summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars-Dominik Braun <lars@6xq.net>2019-09-30 11:39:49 +0200
committerLars-Dominik Braun <lars@6xq.net>2019-09-30 11:44:03 +0200
commit13ecae6eb086ffc15a32e794243cb5b83309c8a8 (patch)
tree8fda44357415264a4fbac6ece35e587710434d5f
parent73d0b5b5addc7782849d462e99c15f87a6255453 (diff)
downloadlulua-13ecae6eb086ffc15a32e794243cb5b83309c8a8.tar.gz
lulua-13ecae6eb086ffc15a32e794243cb5b83309c8a8.tar.bz2
lulua-13ecae6eb086ffc15a32e794243cb5b83309c8a8.zip
stats/render: Add keyboard heatmap rendering
-rw-r--r--lulua/data/render-svg.css4
-rw-r--r--lulua/render.py37
-rw-r--r--lulua/stats.py19
3 files changed, 55 insertions, 5 deletions
diff --git a/lulua/data/render-svg.css b/lulua/data/render-svg.css
index 714c56d..b67b75d 100644
--- a/lulua/data/render-svg.css
+++ b/lulua/data/render-svg.css
@@ -20,6 +20,10 @@ font-size: 40%; font-family: sans-serif;
.button .cap {
fill: #eee8d5;
}
+.button .highlight {
+ fill: #dc322f; /* red */
+ filter: blur(0.5em);
+}
.button.finger-little .shadow {
fill: #dc322f; /* red */
}
diff --git a/lulua/render.py b/lulua/render.py
index b821232..66e5c82 100644
--- a/lulua/render.py
+++ b/lulua/render.py
@@ -37,7 +37,7 @@ RendererSettings = namedtuple ('RendererSetting', ['buttonMargin', 'middleGap',
class Renderer:
""" Keyboard to SVG renderer """
- __slots__ = ('keyboard', 'layout', 'settings', 'cursor', 'writer')
+ __slots__ = ('keyboard', 'layout', 'settings', 'cursor', 'writer', 'keyHighlight')
defaultSettings = RendererSettings (
buttonMargin=0.2,
@@ -47,11 +47,12 @@ class Renderer:
shadowOffset=0.05,
)
- def __init__ (self, keyboard, layout=None, writer=None, settings=None):
+ def __init__ (self, keyboard, layout=None, writer=None, settings=None, keyHighlight=None):
self.keyboard = keyboard
self.layout = layout
self.writer = writer
self.settings = settings or self.defaultSettings
+ self.keyHighlight = keyHighlight or {}
self.cursor = [0, 0]
@@ -120,7 +121,7 @@ class Renderer:
else:
buttonText = list (map (displayText, self.layout.getButtonText (btn)))
- # background rect
+ # background rect if any text
if any (buttonText):
b = svgwrite.shapes.Rect (
insert=((xoff+settings.shadowOffset)*em, (yoff+settings.shadowOffset)*em),
@@ -131,6 +132,7 @@ class Renderer:
g.add (b)
else:
gclass.append ('unused')
+ # main key rect
b = svgwrite.shapes.Rect (
insert=(xoff*em, yoff*em),
size=(width*em, settings.buttonWidth*em),
@@ -160,6 +162,17 @@ class Renderer:
class_='marker')
g.add (l)
+ # highlight rect
+ highlight = self.keyHighlight.get (btn.name, 0)
+ b = svgwrite.shapes.Rect (
+ insert=(xoff*em, yoff*em),
+ size=(width*em, settings.buttonWidth*em),
+ rx=settings.rounded*em,
+ ry=settings.rounded*em,
+ class_='highlight',
+ style=f'opacity: {highlight}')
+ g.add (b)
+
# clock-wise from bottom-left to bottom-right
textParam = [
(-0.5, 0.6, 'layer-1'),
@@ -200,7 +213,12 @@ def renderSvg (args):
layout = defaultLayouts[args.layout].specialize (keyboard)
writer = Writer (layout)
- r = Renderer (keyboard, layout=layout, writer=writer)
+ keyHeat = {}
+ if args.heatmap:
+ maxHeat = max (args.heatmap['buttons'].values ())
+ keyHeat = dict ((k, v/maxHeat) for k, v in args.heatmap['buttons'].items ())
+
+ r = Renderer (keyboard, layout=layout, writer=writer, keyHighlight=keyHeat)
rendered, (w, h) = r.render ()
d = svgwrite.Drawing(args.output, size=(w*em, h*em), profile='full')
d.defs.add (d.style (args.style.read ().decode ('utf-8')))
@@ -307,6 +325,13 @@ def renderKeyman (args):
text = ' '.join ([f'U+{ord (x):04X}' for x in text])
fd.write (f'+ [{comb}] > {text}\n')
+def yamlload (s):
+ try:
+ with open (s) as fd:
+ return yaml.safe_load (fd)
+ except FileNotFoundError:
+ raise argparse.ArgumentTypeError(f'Cannot open file {s}')
+
def render ():
parser = argparse.ArgumentParser(description='Render keyboard into output format.')
parser.add_argument('-l', '--layout', metavar='LAYOUT', help='Keyboard layout name')
@@ -321,6 +346,10 @@ def render ():
# ourselves
type=argparse.FileType('rb'),
help='Include external stylesheet into SVG')
+ sp.add_argument('--heatmap',
+ metavar='FILE',
+ type=yamlload,
+ help='Highlight keys based on heatmap data')
sp.set_defaults (func=renderSvg)
sp = subparsers.add_parser('xmodmap')
sp.set_defaults (func=renderXmodmap)
diff --git a/lulua/stats.py b/lulua/stats.py
index 4cd20bd..7695f39 100644
--- a/lulua/stats.py
+++ b/lulua/stats.py
@@ -18,7 +18,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-import sys, operator, pickle, argparse, logging
+import sys, operator, pickle, argparse, logging, yaml
from operator import itemgetter
from itertools import chain, groupby, product
from collections import defaultdict
@@ -221,6 +221,21 @@ def pretty (args):
effort.addTriads (stats['triads'].triads)
print ('total effort (carpalx)', effort.effort)
+def keyHeatmap (args):
+ stats = pickle.load (sys.stdin.buffer)
+
+ keyboard = defaultKeyboards[args.keyboard]
+ layout = defaultLayouts[args.layout].specialize (keyboard)
+ writer = Writer (layout)
+
+ buttons = {}
+ buttonPresses = sum (stats['simple'].buttons.values ())
+ data = {'total': buttonPresses, 'buttons': buttons}
+ for k, v in sorted (stats['simple'].buttons.items (), key=itemgetter (1)):
+ assert k.name not in data
+ buttons[k.name] = v
+ yaml.dump (data, sys.stdout)
+
def main ():
parser = argparse.ArgumentParser(description='Process statistics files.')
parser.add_argument('-l', '--layout', metavar='LAYOUT', help='Keyboard layout name')
@@ -239,6 +254,8 @@ def main ():
sp.add_argument('-s', '--sort', choices={'weight', 'effort', 'combined'}, default='weight', help='Sorter')
sp.add_argument('-n', '--limit', type=int, default=0, help='Sorter')
sp.set_defaults (func=triadfreq)
+ sp = subparsers.add_parser('keyheatmap')
+ sp.set_defaults (func=keyHeatmap)
logging.basicConfig (level=logging.INFO)
args = parser.parse_args()