ActivityWatch/aw-webui

View on GitHub
src/visualizations/periodusage.js

Summary

Maintainability
A
2 hrs
Test Coverage
'use strict';

const d3 = require('d3');
const _ = require('lodash');
const moment = require('moment');

import { seconds_to_duration, get_hour_offset } from '../util/time';

function create(svg_elem) {
  // Clear element
  svg_elem.innerHTML = '';
}

function set_status(svg_elem, msg) {
  // Select svg canvas
  svg_elem.innerHTML = '';
  const svg = d3.select(svg_elem);

  svg
    .append('text')
    .attr('x', '50%')
    .attr('y', '25pt')
    .text(msg)
    .attr('font-family', 'sans-serif')
    .attr('font-size', '20pt')
    .attr('text-anchor', 'middle')
    .attr('fill', '#AAA');
}

const diagramcolor = '#aaa';
const diagramcolor_selected = '#fc5';
const diagramcolor_focused = '#adf';

function update(svg_elem, usage_arr, onPeriodClicked) {
  const dateformat = 'YYYY-MM-DD';

  // No apps, sets status to "No data"
  if (usage_arr.length <= 0) {
    set_status(svg_elem, 'No data');
    return;
  }
  svg_elem.innerHTML = '';
  const svg = d3.select(svg_elem);

  function get_usage_time(day_events) {
    const day_event = _.head(_.filter(day_events, e => e.data.status == 'not-afk'));
    return day_event != undefined ? day_event.duration : 0;
  }

  const usage_times = usage_arr.map(day_events => get_usage_time(day_events));
  let longest_usage = Math.max.apply(null, usage_times);
  // Avoid division by zero
  if (longest_usage <= 0) {
    longest_usage = 0.00000000001;
  }

  const padding = 0.3 * (100 / (usage_arr.length - 1));
  const width = 100 / usage_arr.length - padding;
  const center_elem = Math.floor(usage_arr.length / 2);

  _.each(usage_arr, (events, i) => {
    const usage_time = get_usage_time(events);
    const height = 85 * (usage_time / longest_usage);
    let date = '';
    if (events.length > 0) {
      // slice off so it's only the day
      date = moment(events[0].timestamp).subtract(get_hour_offset(), 'hours').format(dateformat);
    }
    const color = i == center_elem ? diagramcolor_selected : diagramcolor;
    const offset = 50;

    const x = i * padding + i * width + 0.25 * width;

    // FIXME: Doesn't work well, notably breaks on last7d and last30d
    if (moment(date).isSame(moment(), 'day')) {
      svg
        .append('line')
        .attr('x1', x + width / 2 + '%')
        .attr('y1', 0)
        .attr('x2', x + width / 2 + '%')
        .attr('y2', 200)
        .attr('style', 'stroke: #888; stroke-width: 2px;')
        .attr('stroke-dasharray', '4, 2');

      svg
        .append('text')
        .attr('x', x + 1.5 * width + '%')
        .attr('y', '30')
        .text('Today');
    }

    const rect = svg
      .append('rect')
      .attr('x', x + '%')
      .attr('y', 101 - height + '%') // Draw rect bottom-up
      .attr('rx', 3)
      .attr('ry', 3)
      .attr(
        'style',
        i == center_elem ? 'stroke: black; stroke-width: 1;' : 'stroke: #222; stroke-width: 1;'
      )
      .attr('width', width + '%')
      .attr('height', height + offset + '%')
      .attr('color', color)
      .attr('date', date)
      .style('fill', color)
      .on('mouseover', () => {
        rect.style('fill', diagramcolor_focused);
      })
      .on('mouseout', e => {
        rect.style('fill', e.target.attributes.color.value);
      })
      .on('click', function () {
        onPeriodClicked(date);
      });
    rect.append('title').text(date + '\n' + seconds_to_duration(usage_time));
  });
}

export default {
  create: create,
  update: update,
  set_status: set_status,
};