1.0.28 • Published 4 years ago

periphery-plots v1.0.28

Weekly downloads
6
License
Apache-2.0
Repository
github
Last release
4 years ago

Periphery Plots from the Precision VISSTA Team

Patterns in temporal data are often across different scales, such as days, weeks, and months, making effective visualization of time-based data challenging. Periphery Plots are a new approach for providing focus and context in time-based charts to enable interpretation of patterns across heterogeneous time scales. Our approach employs a focus zone with a time axis and a second axis, that can either represent quantities or categories, as well as a set of adjacent periphery plots that aggregate data along the time dimension, value dimension, or both. This repository contains a prototype implementation of Periphery Plots as a React component as well as demo of the technique.

Developed by our teams from the University of North Carolina at Chapel Hill (Bryce Morrow, David Gotz, Arlene Chung) and from Harvard Medical School (Trevor Manz, Nils Gehlenborg) through funding support from the NIH NIBIB and Office of the Director.

Contact information: qubbdr01@gmail.com

npm.io

How to Use

  1. Live demo of the app visualizing a sample dataset of weather in seattle over multiple years.

  2. Local install from github:

    Requires the latest version of NodeJS.

    1. git clone https://github.com/PrecisionVISSTA/PeripheryPlots.git
    2. cd PeripheryPlots
    3. npm i
    4. npm i react react-dom
    5. npm start
  3. Install into an existing project via npm:

    Requires the latest version of NodeJS.

    1. npm i periphery-plots
    2. npm i react react-dom
      • Only complete step 2 if you do not already have compatible versions of react and react-dom in your project already. See the reasoning here.

Preprint

A preprint describing the periphery plot data visualization approach in detail is available on arxiv: https://arxiv.org/abs/1906.07637.

Winner of the Best Short Paper award at IEEE VIS 2019.

Component Configuration

The PeripheryPlots React component takes a single configuration object as input. Properties that are bolded are required while all others are optional with sensible defaults. The React based prop-types library is used to validate the configuration object.

PropertyTypeDescription
trackwiseObservations[ Object, ... , ... ]The set of temporal observations for each track.
trackwiseTimeKeys String, ... Key used to index temporal attribute from observation objects.
trackwiseValueKeys String, ... Key used to index value attribute from observation objects.
trackwiseTypes String, ... Data type for each track. Can be "continuous", "discrete", or "other".
trackwiseUnits String OR Null, ... Unit for each track.
trackwiseNumAxisTicks Integer+ OR Null, ... The number of ticks for each track axis.
trackwiseAxisTickFormatters d3.format OR Null, ... Tick formatter for each track axis.
trackwiseEncodings[ [ React.Component, ... , ... ] ... ]Layered encoding specification for each track.
applyEncodingsUniformlyBooleanDetermines the number of encoding specifications required for each track.
contextWidthRatioFloat in range 0.0, 1.0 default: .2Fraction of available space (whithin tracks) allocated to each context plot.
numContextsPerSideInteger+ default: 1The number of context zones on each side of the focus zone.
timeExtentDomain Date, Date A temporal range including all data observations across all data sources.
timeDomains[ Date, Date , ... ]Temporal ranges corresponding to initially selected brush regions for the control timeline.
tickIntervald3.intervalThe interval for tick placement for the control timeline axis.
dZoomInteger+ default: 5Speed of track generated zoom events for control timeline. For wide screens, it may be necessary to increase this value
containerBackgroundColorValid input to d3.color default: "#ffffff"Background color for the component container.
focusColorValid input to d3.color default: "#576369"Color of focus brush and focus plot borders.
contextColorValid input to d3.color default: "#9BB1BA"Color of context brush and context plot borders.
lockActiveColorValid input to d3.color default: "#00496E"Color of control timeline locks when active.
lockInactiveColorValid input to d3.color default: "Grey"Color of control timeline locks when inactive.
containerPaddingInteger+ default: 10Component padding in pixels that surrounds tracks and control timeline.
controlTimelineHeightInteger+ default: 50The height of the control timeline in pixels.
verticalAlignerHeightInteger+ default: 30The height of the vertical alignment component in pixels.
axesWidthInteger+ default: 40The width of the axis for each track in pixels.
trackHeightInteger+ default: 50The width of each track in pixels.
trackSvgOffsetTopInteger+ default: 10The offset from top of svg plot containers defining top bound on drawable space.
trackSvgOffsetBottomInteger+ default: 5The offset from bottom of svg plot containers defining bottom bound on drawable space.
trackSvgOffsetBottomInteger+ default: 5The offset from bottom of svg plot containers defining bottom bound on drawable space.
formatTrackHeaderFunction(valueKey, unit) default: removes underscores and adds a space between valueKey and unit.Function to format header string for each track receiving valueKey and unit as inputs
msecsPaddingInteger default: 0When determining the observations bound to an individual plot, we expand the plot's bound time domain by msecsPadding time units on both sides. This property is helpful for encodings like line charts, which may appear discontinuous near the boundaries of the container if only the data points within the interval are visualized.
lockOutlineColorValid input to d3.color default: "#000000"The outline color for timeline control locks.
handleOutlineColorValid input to d3.color default: "#000000"The outline color for timeline control handles.
brushOutlineColorValid input to d3.color default: "#000000"The outline color for timeline control brushes.

Some of the descriptions in the table above are sufficient, but some properties are more complex and must satisfy specific criteria to be considered valid.

We describe these properties in further detail as they relate to the two core subcomponents of the PeripheryPlots framework:

  • Tracks
  • Control Timeline

Tracks

Tracks are vertically aligned containers that house multiple focus and context plots. The plots are horizontally organized along the temporal axis. The focus plot is always in the middle and there are an equal number of context plots on both sides of the focus plot.

All properties that begin with the word 'trackwise' are arrays and they must have equal lengths. The ith value in each of these arrays corresponds to some property for the ith track.

trackwiseObservations

Each individual set of observations is an array of objects. Each object is a temporal observation with a temporal attribute and one or more value attributes.

trackwiseTypes

We broadly group data into three classes: Continuous (assumed to be numeric) Discrete (can be numeric or of some other form) * Other These enumerative types are used to determine what type of axis is used for each track.

trackwiseUnits

If specified, the unit is displayed alongside the track name (or rather, the valueKey used to index observations).

trackwiseEncodings

For each track we specify a collection of encoding schemas. The dimensions of trackwiseEncodings is determined by applyContextEncodingsUniformly and numContextsPerSide.

Each encoding schema, represented by trackwiseEncodings[i][j] is an array where each element is a React.Component (can be class, function, or any other kind). Each possible value of trackwiseEncodings[i][j] is bound to some plot within the interface (as described above). After this binding occurs, the elements of trackwiseEncodings[i][j] are rendered into the plot (svg) they are bound to in the order they are specified.

  • ex: if trackwiseEncodings[i][j] = BarChart, AverageLineGroup, then some plot within some track will be populated with a bar chart visualization with an average line annotation layered over top. You can see an example of this in the 'precipitation' track in the .gif demo at the top of this page.

Control Timeline

The control timeline is a set of multiple linked brushes which allow users to dynamically configure focus and context zones, temporal regions to be viewed and summarized at varying visual resolutions.

The ith brush (from left to right) in the control timeline determines the temporal period bound to the ith subplot (from left to right) across all tracks in the interface.

timeExtentDomain

A temporal range containing all points. This should almost always be as small as possible, so there is no temporal subrange for which there is no data.

timeDomains

The initial temporal ranges of analysis. There should be (numContextsPerSide * 2) + 1 timeDomains specified. There should be no temporal distance between two adjacent temporal ranges (i.e. where timeDomains[i] ends, timeDomains[i+1] should begin).

Component Styling via CSS

Many of the internal style properties for the component must be specified through the configuration object to ensure only certain style properties of sub-components are changed.

One area where this is not as much of a problem is styling the text that appears within the component. We have given the text elements within the component special class names to allow for custom styling via CSS.

ClassDescription
pplot-control-timeline-textControl timeline axis labels.
pplot-track-header-textHeader text for each track.
pplot-track-header-text-containerContainer (div) for header text for each track. This container can control the text-alignment of the track label.
pplot-track-axis-textAxis text for each track.

Creating Custom Encodings

You can create your own custom encodings for use with the PeripheryPlots component. Simply write a React component, import it, and include it somewhere in trackwiseEncodings. All components in trackwiseEncodings will recieve a object called pplot which contains a number of properties that can be used to plot data within a corresponding plot.

PropertyTypeDescription
observations Object, ... all observations within timeDomain.
timeKeyStringThe temporal index into each individual observation object.
valueKeyStringThe value index into each individual observation object.
timeDomainDate, DateThe temporal range for the plot.
valueDomain Number, Number OR Object, ... OR NullRange of possible values across all observations.
xRange Number, Number Plottable range in x dimension.
yRange Number, Number Plottable range in y dimension.
scaleRangeToBoxFunction (d3.scale, d3.scale)Binds input d3.scale objects to plottable ranges (x and/or y).
isLeftBooleanWhether or not the encoding is bound to a left context plot.
isFocusBooleanWhether or not the encoding is bound to focus plot.
getAllObservationsFunction()Escape hatch that allows an encoding bound to any plot to access all observations, not just observations within a set time domain. This should only be used for encodings that require knowledge of other data points to generate their own visual representation (for performance reasons). An example would be a 21 day moving average encoding.

We describe some of these properties in more detail below.

valueDomain

The value of valueDomain depends on the type of the current track (specified in trackwiseTypes in the configuration object)

  • if type === "continuous":
    • valueDomain is of the form Number, Number and represents the minimum and maximum numerical values seen across all observations.
  • if type === "discrete":
    • valueDomain is of the form Object, ... and represents all unique values seen across all observations.
  • if type === "other":
    • valueDomain is Null.

scaleRangeToBox

a function that takes two d3.scale objects as input. Sets the range of the first scale to the xRange and sets the range of the second scale to yRange. Either scale input can be omitted (Null value) if the encoding only requires a single scale to have its range set.

This can be done manually by the programmer but since the operation is so common we still provide this utility function.

isLeft and isFocus

These two Boolean values can be used to determine what kind of plot the encoding is bound to. There are sometimes cases where this information is useful.

For example, when using either the QuantitativeTracePlot or NominalTracePlot encodings that come packaged with the framework, we often want to flip the encoding about the y axis to preserve symmetry relative to the focus plot. You can see an example of this in the 'temp max' and 'weather' tracks in the gif demo at the top of this page.

yRange and yRange

It's important to note that both yRange and yRange are monotonically increasing ranges (i.e. range1 > range0). As is typical in computer graphics, the y-coordinate system begins from the top of the container and extends towards the bottom of the container as the y-coordinate increases. If you want your custom encoding to use a coordinate system where (0, 0) is in the lower left corner of the plot and (width, height) is in the upper right hand corner of the plot, you can flip the yRange before setting it as the range for your scale.

Default Encodings

The PeripheryPlots framework has a number of encodings implemented by default.

Any one of these default encodings can be used as a reference template when developing custom encodings.

BarGroup - Quantitative bar chart.

EventGroup - Categorical event timeline where each event is a rectangle. Rectangles are colored by event type.

LineGroup - Quantitative line chart.

MovingAverageEnvelopeGroup - Envelope computed using 10 day moving average.

ScatterGroup - Quantitative scatter chart.

AverageLineGroup - Average line annotation.

NominalTraceGroup - Categorical sideways histogram.

QuantitativeTraceGroup - Quantitative sideways histogram.

Custom Encoding Example

Here is an example of what a line plot encoding might look like using the new React Hooks API.

import React, { useState } from "react";
import { line, curveMonotoneX } from 'd3-shape'; 
import { scaleLinear, scaleTime } from 'd3-scale'; 

export default function LineGroup(props) {

    let [line, setLine] = useState(() => line().curve(curveMonotoneX)); 
    let [timeScale, setTimeScale] = useState(() => scaleTime()); 
    let [valueScale, setValueScale] = useState(() => scaleLinear()); 

    let { pplot } = props; 
    let { timeKey, valueKey, timeDomain, valueDomain, observations, scaleRangeToBox } = pplot; 

    let scales = scaleRangeToBox(timeScale, valueScale); 
    timeScale = scales.xScale; 
    valueScale = scales.yScale; 

    timeScale.domain(timeDomain); 
    valueScale.domain(valueDomain); 

    line.x(d => timeScale(d[timeKey]))
        .y(d => valueScale(d[valueKey]));

    return (
        <g>
            {/* Line */}
            <path d={line(observations)} fill="none" stroke="steelblue"/>
        </g>
    ); 

}
1.0.28

4 years ago

1.0.27

4 years ago

1.0.26

4 years ago

1.0.25

4 years ago

1.0.24

4 years ago

1.0.23

4 years ago

1.0.22

4 years ago

1.0.21

4 years ago

1.0.20

4 years ago

1.0.19

4 years ago

1.0.18

4 years ago

1.0.17

4 years ago

1.0.16

4 years ago

1.0.15

4 years ago

1.0.14

4 years ago

1.0.13

4 years ago

1.0.12

4 years ago

1.0.11

4 years ago

1.0.10

5 years ago

1.0.9

5 years ago

1.0.8

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago