tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

graph-helper.js (10232B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 // BOUND_EXCLUDING_TIME should be less than 1ms and is used to exclude start
      8 // and end bounds when dividing duration in createPathSegments.
      9 const BOUND_EXCLUDING_TIME = 0.001;
     10 // We define default graph height since if the height of viewport in SVG is
     11 // too small (e.g. 1), vector-effect may not be able to calculate correctly.
     12 const DEFAULT_GRAPH_HEIGHT = 100;
     13 // Default animation duration for keyframes graph.
     14 const DEFAULT_KEYFRAMES_GRAPH_DURATION = 1000;
     15 // DEFAULT_MIN_PROGRESS_THRESHOLD shoud be between more than 0 to 1.
     16 const DEFAULT_MIN_PROGRESS_THRESHOLD = 0.1;
     17 // In the createPathSegments function, an animation duration is divided by
     18 // DEFAULT_DURATION_RESOLUTION in order to draw the way the animation progresses.
     19 // But depending on the timing-function, we may be not able to make the graph
     20 // smoothly progress if this resolution is not high enough.
     21 // So, if the difference of animation progress between 2 divisions is more than
     22 // DEFAULT_MIN_PROGRESS_THRESHOLD * DEFAULT_GRAPH_HEIGHT, then createPathSegments
     23 // re-divides by DEFAULT_DURATION_RESOLUTION.
     24 // DEFAULT_DURATION_RESOLUTION shoud be integer and more than 2.
     25 const DEFAULT_DURATION_RESOLUTION = 4;
     26 // Stroke width for easing hint.
     27 const DEFAULT_EASING_HINT_STROKE_WIDTH = 5;
     28 
     29 /**
     30 * The helper class for creating summary graph.
     31 */
     32 class SummaryGraphHelper {
     33  /**
     34   * Constructor.
     35   *
     36   * @param {object} state
     37   *        State of animation.
     38   * @param {Array} keyframes
     39   *        Array of keyframe.
     40   * @param {number} totalDuration
     41   *        Total displayable duration.
     42   * @param {number} minSegmentDuration
     43   *        Minimum segment duration.
     44   * @param {Function} getValueFunc
     45   *        Which returns graph value of given time.
     46   *        The function should return a number value between 0 - 1.
     47   *        e.g. time => { return 1.0 };
     48   * @param {Function} toPathStringFunc
     49   *        Which returns a path string for 'd' attribute for <path> from given segments.
     50   */
     51  constructor(
     52    state,
     53    keyframes,
     54    totalDuration,
     55    minSegmentDuration,
     56    getValueFunc,
     57    toPathStringFunc
     58  ) {
     59    this.totalDuration = totalDuration;
     60    this.minSegmentDuration = minSegmentDuration;
     61    this.minProgressThreshold =
     62      getPreferredProgressThreshold(state, keyframes) * DEFAULT_GRAPH_HEIGHT;
     63    this.durationResolution = getPreferredDurationResolution(keyframes);
     64    this.getValue = getValueFunc;
     65    this.toPathString = toPathStringFunc;
     66 
     67    this.getSegment = this.getSegment.bind(this);
     68  }
     69 
     70  /**
     71   * Create the path segments from given parameters.
     72   *
     73   * @param {number} startTime
     74   *        Starting time of animation.
     75   * @param {number} endTime
     76   *        Ending time of animation.
     77   * @return {Array}
     78   *         Array of path segment.
     79   *         e.g.[{x: {Number} time, y: {Number} progress}, ...]
     80   */
     81  createPathSegments(startTime, endTime) {
     82    return createPathSegments(
     83      startTime,
     84      endTime,
     85      this.minSegmentDuration,
     86      this.minProgressThreshold,
     87      this.durationResolution,
     88      this.getSegment
     89    );
     90  }
     91 
     92  /**
     93   * Return a coordinate as a graph segment at given time.
     94   *
     95   * @param {number} time
     96   * @return {object}
     97   *         { x: Number, y: Number }
     98   */
     99  getSegment(time) {
    100    const value = this.getValue(time);
    101    return { x: time, y: value * DEFAULT_GRAPH_HEIGHT };
    102  }
    103 }
    104 
    105 /**
    106 * Create the path segments from given parameters.
    107 *
    108 * @param {number} startTime
    109 *        Starting time of animation.
    110 * @param {number} endTime
    111 *        Ending time of animation.
    112 * @param {number} minSegmentDuration
    113 *        Minimum segment duration.
    114 * @param {number} minProgressThreshold
    115 *        Minimum progress threshold.
    116 * @param {number} resolution
    117 *        Duration resolution for first time.
    118 * @param {Function} getSegment
    119 *        A function that calculate the graph segment.
    120 * @return {Array}
    121 *         Array of path segment.
    122 *         e.g.[{x: {Number} time, y: {Number} progress}, ...]
    123 */
    124 function createPathSegments(
    125  startTime,
    126  endTime,
    127  minSegmentDuration,
    128  minProgressThreshold,
    129  resolution,
    130  getSegment
    131 ) {
    132  // If the duration is too short, early return.
    133  if (endTime - startTime < minSegmentDuration) {
    134    return [getSegment(startTime), getSegment(endTime)];
    135  }
    136 
    137  // Otherwise, start creating segments.
    138  let pathSegments = [];
    139 
    140  // Append the segment for the startTime position.
    141  const startTimeSegment = getSegment(startTime);
    142  pathSegments.push(startTimeSegment);
    143  let previousSegment = startTimeSegment;
    144 
    145  // Split the duration in equal intervals, and iterate over them.
    146  // See the definition of DEFAULT_DURATION_RESOLUTION for more information about this.
    147  const interval = (endTime - startTime) / resolution;
    148  for (let index = 1; index <= resolution; index++) {
    149    // Create a segment for this interval.
    150    const currentSegment = getSegment(startTime + index * interval);
    151 
    152    // If the distance between the Y coordinate (the animation's progress) of
    153    // the previous segment and the Y coordinate of the current segment is too
    154    // large, then recurse with a smaller duration to get more details
    155    // in the graph.
    156    if (Math.abs(currentSegment.y - previousSegment.y) > minProgressThreshold) {
    157      // Divide the current interval (excluding start and end bounds
    158      // by adding/subtracting BOUND_EXCLUDING_TIME).
    159      const nextStartTime = previousSegment.x + BOUND_EXCLUDING_TIME;
    160      const nextEndTime = currentSegment.x - BOUND_EXCLUDING_TIME;
    161      const segments = createPathSegments(
    162        nextStartTime,
    163        nextEndTime,
    164        minSegmentDuration,
    165        minProgressThreshold,
    166        DEFAULT_DURATION_RESOLUTION,
    167        getSegment
    168      );
    169      pathSegments = pathSegments.concat(segments);
    170    }
    171 
    172    pathSegments.push(currentSegment);
    173    previousSegment = currentSegment;
    174  }
    175 
    176  return pathSegments;
    177 }
    178 
    179 /**
    180 * Create a function which is used as parameter (toPathStringFunc) in constructor
    181 * of SummaryGraphHelper.
    182 *
    183 * @param {number} endTime
    184 *        end time of animation
    185 *        e.g. 200
    186 * @param {number} playbackRate
    187 *        playback rate of animation
    188 *        e.g. -1
    189 * @return {Function}
    190 */
    191 function createSummaryGraphPathStringFunction(endTime, playbackRate) {
    192  return segments => {
    193    segments = mapSegmentsToPlaybackRate(segments, endTime, playbackRate);
    194    const firstSegment = segments[0];
    195    let pathString = `M${firstSegment.x},0 `;
    196    pathString += toPathString(segments);
    197    const lastSegment = segments[segments.length - 1];
    198    pathString += `L${lastSegment.x},0 Z`;
    199    return pathString;
    200  };
    201 }
    202 
    203 /**
    204 * Return preferred duration resolution.
    205 * This corresponds to narrow interval keyframe offset.
    206 *
    207 * @param {Array} keyframes
    208 *        Array of keyframe.
    209 * @return {number}
    210 *         Preferred duration resolution.
    211 */
    212 function getPreferredDurationResolution(keyframes) {
    213  if (!keyframes) {
    214    return DEFAULT_DURATION_RESOLUTION;
    215  }
    216 
    217  let durationResolution = DEFAULT_DURATION_RESOLUTION;
    218  let previousOffset = 0;
    219  for (const keyframe of keyframes) {
    220    if (previousOffset && previousOffset != keyframe.offset) {
    221      const interval = keyframe.offset - previousOffset;
    222      durationResolution = Math.max(
    223        durationResolution,
    224        Math.ceil(1 / interval)
    225      );
    226    }
    227    previousOffset = keyframe.offset;
    228  }
    229 
    230  return durationResolution;
    231 }
    232 
    233 /**
    234 * Return preferred progress threshold to render summary graph.
    235 *
    236 * @param {object} state
    237 *        State of animation.
    238 * @param {Array} keyframes
    239 *        Array of keyframe.
    240 * @return {float}
    241 *         Preferred threshold.
    242 */
    243 function getPreferredProgressThreshold(state, keyframes) {
    244  const steps = getStepsCount(state.easing);
    245  const threshold = Math.min(DEFAULT_MIN_PROGRESS_THRESHOLD, 1 / (steps + 1));
    246 
    247  if (!keyframes) {
    248    return threshold;
    249  }
    250 
    251  return Math.min(
    252    threshold,
    253    getPreferredProgressThresholdByKeyframes(keyframes)
    254  );
    255 }
    256 
    257 /**
    258 * Return preferred progress threshold by keyframes.
    259 *
    260 * @param {Array} keyframes
    261 *        Array of keyframe.
    262 * @return {float}
    263 *         Preferred threshold.
    264 */
    265 function getPreferredProgressThresholdByKeyframes(keyframes) {
    266  let threshold = DEFAULT_MIN_PROGRESS_THRESHOLD;
    267 
    268  for (let i = 0; i < keyframes.length - 1; i++) {
    269    const keyframe = keyframes[i];
    270 
    271    if (!keyframe.easing) {
    272      continue;
    273    }
    274 
    275    const steps = getStepsCount(keyframe.easing);
    276 
    277    if (steps) {
    278      const nextKeyframe = keyframes[i + 1];
    279      threshold = Math.min(
    280        threshold,
    281        (1 / (steps + 1)) * (nextKeyframe.offset - keyframe.offset)
    282      );
    283    }
    284  }
    285 
    286  return threshold;
    287 }
    288 
    289 function getStepsCount(easing) {
    290  const stepsFunction = easing.match(/(steps)\((\d+)/);
    291  return stepsFunction ? parseInt(stepsFunction[2], 10) : 0;
    292 }
    293 
    294 function mapSegmentsToPlaybackRate(segments, endTime, playbackRate) {
    295  if (playbackRate > 0) {
    296    return segments;
    297  }
    298 
    299  return segments.map(segment => {
    300    segment.x = endTime - segment.x;
    301    return segment;
    302  });
    303 }
    304 
    305 /**
    306 * Return path string for 'd' attribute for <path> from given segments.
    307 *
    308 * @param {Array} segments
    309 *        e.g. [{ x: 100, y: 0 }, { x: 200, y: 1 }]
    310 * @return {string}
    311 *         Path string.
    312 *         e.g. "L100,0 L200,1"
    313 */
    314 function toPathString(segments) {
    315  let pathString = "";
    316  segments.forEach(segment => {
    317    pathString += `L${segment.x},${segment.y} `;
    318  });
    319  return pathString;
    320 }
    321 
    322 exports.createPathSegments = createPathSegments;
    323 exports.createSummaryGraphPathStringFunction =
    324  createSummaryGraphPathStringFunction;
    325 exports.DEFAULT_DURATION_RESOLUTION = DEFAULT_DURATION_RESOLUTION;
    326 exports.DEFAULT_EASING_HINT_STROKE_WIDTH = DEFAULT_EASING_HINT_STROKE_WIDTH;
    327 exports.DEFAULT_GRAPH_HEIGHT = DEFAULT_GRAPH_HEIGHT;
    328 exports.DEFAULT_KEYFRAMES_GRAPH_DURATION = DEFAULT_KEYFRAMES_GRAPH_DURATION;
    329 exports.getPreferredProgressThresholdByKeyframes =
    330  getPreferredProgressThresholdByKeyframes;
    331 exports.SummaryGraphHelper = SummaryGraphHelper;
    332 exports.toPathString = toPathString;