SOCR HTML5 Expansion MotionCharts

From SOCR
Jump to: navigation, search

SOCR Project - SOCR HTML5 Expansion of the MotionCharts Project

Background

SOCR MotionChart Web-app

The amount, complexity and provenance of data have dramatically increased in the past five years. Visualization of observed and simulated data is a critical component of any social, environmental, biomedical or scientific quest. The SOCR MotionCharts provide an interactive infrastructure for discovery-based exploratory analysis of multivariate data. This dynamic data visualization tool enables the displaying of high-dimensional longitudinal data. SOCR Motion Charts allows mapping of ordinal, nominal and quantitative variables onto time, 2D axes, size, colors, glyphs and appearance characteristics, which facilitates the interactive display of multidimensional data.

Project goals

The goal of this project is to redesign the Java-based SOCR_MotionCharts applet using only HTML5, CSS3 and JavaScript, and in the process introduce some useful and powerful expansions of this web-app.

Project specification

The HTML5/JavaScript implementation of the new SOCR MotionCharts Web-App es expected to lower the device and software barriers for users. There are three specific extensions to the Motion Charts application that are necessary in this redesign process

SOCR MotionChart Color Spectrum Web-app Improvement
  1. The first extension/improvement provides better color handling for data that are not uniformly distributed. The proposed solution requires the use of a percentile or quantile subdivision of data intensities. Given a data set , the current color indexing calculations would assign too many colors to empty spaces, and too few to the actual clusters of data. While we could simply create a color map of all possible color values, the problem would still exist with data that have large ranges and it would be incredibly memory inefficient. A quantile-based color index would assign more colors to areas of greater data density and fewer colors to areas of lesser data density, reducing the size of the color map and providing distinct colors to more points. Suppose we have N distinct observations and we choose to have C distinct colors, the quintile index could be calculated by T = N/C. Each quantile would contain T observations and would be assigned an appropriate color value based on their intensities. The image below shows the idea behind this SOCR Motion Charts improvement allowing input of significantly skewed data (top, A). Instead of using the default uniform color distribution over the support of the data distribution (middle, B), the new approach will employ a quantile-based color indexing to assign colors according to the true data density (bottom, C).
    SOCR MotionChart Shape Appearance Web-app Improvement
  2. The second proposed extension involves supporting a greater number of dimensions. Currently, the x-coordinate, y-coordinates, size, and color of the blob are the supported dimensions (excluding the temporal/indexed component). The proposed extension involves including the eccentricity, fill pattern, and stroke shape as extra dimensions. The eccentricity of the blob would indicate the directionality of the data and would be specified by a 2x2 variance-covariance matrix. The fill pattern of the blob could indicate data frequency—the higher the frequency of the data, the higher the frequency of the fill pattern. Finally, the stroke shape of the blob refers to the oscillatory complexity of the blob boundary. This dimension could be mapped to any ordered value. The higher the value in relation to other values in the column, the greater the boundary complexity would be, see Figure 8B. The boundary complexities could be determined through sine-wave based methods or fractal based methods. The figure below shows the SOCR Motion Charts improvement providing blob-appearance cues. For example, the blob fill pattern may indicate data frequency (left, A), and the shape of the blob boundary may represent another variable (right, B).
See this Blob-Shape HTML5/JS Example that illustrates an easy way to MAP several additional data variables into Shape characteristics: Each type of shape (e.g., clover, bean, triangle, etc.) could represent a different variable. Then the parameters of the specific shape (e.g., m, n1, n2, n3, a, b) can be chosen according to the values of the variable mapped to shape, reflecting the amount of deviation from the standard shape (identity/default values for the shape).
  1. Smooth Motion Animation: Provide a preference-based mechanism for controlling the smoothness of the motion-chart animation with time. See the options of PowerPoint's animation interface (e.g., delays, smooth starting and ending points, etc.)

Exemplary tools that can be employed

Social networking/sharing

The HTML5 MotionChart should allow users that have online data to share their entire web-app state (in playable format) with any other user using unique URL's. For example:

http://SOCR.ucla.edu/htmls/HTML5/MotionChart/MotionChart.php?http://wiki.stat.ucla.edu/socr/index.php/SOCR_Data_Dinov_010309_HousingPriceIndex&key=year&x=HPI&y=UR&size=pop&color=region&category=State&play=true

This unique URL motion-chart play format uses the following components:

See this example using similar invocation protocol for the Pipeline environment (http://ucla.in/xIL1E8).


Documentation

The materials below provide the development documentation for the HTML5-based SOCR MotionCharts Webapp (see the source code).

Introduction

Motion Chart HTML5 Documentation is a detailed documentation of SOCR's Motion Chart jQuery plugin.

Overview

The jQuery plugin was designed with a Model–view–controller structure in mind. It is not strictly enforced due to jQuery's plugins’ callback nature. Nevertheless it helps organize different aspects of the plugin. The core components of Motion Chart are Priv, View, Controller and Chart. Additionally there’s a settings object and motionchart function.

Priv

Contains Motion Chart’s private variables

  • settings: Inherits options from object settings or (if specified) user options
  • dom: jQuery objects
  • playing: flag to indicate if the chart is in motion
  • ie9: flag to indicate if browser used is internet explorer
  • menuHeight: height of the menu (global for consistancy issues)

View

The view object constructs and maintains Motion Chart’s DOM. This includes constructing sliders, tables, tooltips, menus and resizing components.

  • Build: Constructs the DOM skeleton for the motionchart instance and stores the jquery references in priv.dom to minimize DOM lookups.
  • Sliders: Creates two sliders:
    • Documentation available on http://jqueryui.com/demos/slider/
    • mainSlider (used to control the chart)
      • min: always set to 0
      • max: initialized to 1, set to the maximum key length during axis key change
      • step: always set to 1
      • animate: initialized to priv.settings.speed, updated when speedSlider is changed
      • change: when the slider is altered chart.update is called with the slider’s value as the parameter. Meaning the slider triggers the chart to update to a new key
    • speedSlider (used to control the motion speed)
      • min: initialized to priv.settings.minSpeed, defaulted at 1000
      • max: initialized to priv.settings.maxSpeed, defaulted at 6000
      • step: always set to 500
      • orientation: vertical
      • slide: everytime the slider is triggered to slide, show tooltip (which contains the speed value) Might be removed
      • change: when the slider is altered update chart’s duration and mainSlider’s animation speed
  • table: Constructs a handsontable instance in priv.dom.$table ** rows: 10, initial number of rows ** cols: 10, initial number of columns ** minSpareRows: 1, minimum number of empty rows to maintain at the end ** min SpareCols: 1, minimum number of empty columns to maintain at the end ** contextMenu: true, allow right click options ** onChange: on table change update chart data and reset mappings * tooltips ** mainSlider handler: displays slider value ** play/pause button: displays info. ** backward button: displays info. ** forward button: displays info. * initWindow ** If container is smaller than priv.settings.minWidth/minHeight, resizes container to priv.settings.minWidth/minHeight respectively. ** Resizes dom components from the top ($content) down ($play) * resize: Applies resizable plugin to $svg
    • minHeight: priv.settings.minHeight, minimum $svg height ** minWidth: priv.settings.minWidth, minimum $svg width
    • handles: se, place a handle only on the south east (bottom right) corner
    • resize: when resized
      • resizes the dom from the bottom ($svg) up (container) *** calls chart.resize() to resize the SVG element including axes and nodes’ positioning * Context Menu: initializes all Context menus ** $svg context menu: Covers all mappings, scales, colormaps and Save As Image
      • selector: .svg, bind context menu to .svg
      • trigger: none, we will create a custom trigger (right-click) in controller.contextmenu to bind the trigger to specific motionchart instances
      • build: returns object containing list of menu elements and callbacks
      • items:
        • map: view.keyItems.getMapItems() passed through $trigger.data(“items”) ****scale : ***** items: linear - log (logarithm) - sqrt (square root) - exponential (squared) which is mapped in a switch in chart.setScale(scale) ***** callback: controller.scaleCallback function passed through $trigger.data(“scaleCallback”)
        • setcolor: view.keyItems.getColorItems() passed through $trigger.data("colorItems") ** X-Axis label menu: Covers x-axis mappings only ** Y-Axis label menu: Covers y-axis mappings only ** Interactive Menu menu: Covers mappings, scales and colormaps separately ===Controller=== The controller handles all the user interactions within a Motion Chart instance. This includes buttons and menus. * buttons ** $tabs (Chart/Data)
      • When clicked toggles $chart and $table
      • When going to chart updateData() and setMappings() are called to rebind data and reset mappings
    • $play *** if $play has class ‘pause’
        • Stop ongoing animation (playState interval
        • hide tooltip after 1000 ms
      • if mainSlider handler is at the end
        • Display the tooltip for 1000 ms and do nothing
      • Otherwise (play the animation)
        • Add class ‘pause’
        • Display tooltip
        • increment mainSlider’s value (which causes chart to animate to the next key)
        • set playState interval (repeats every speedSlider value)
          • Display tooltip
          • Increment mainSlider’s value (which causes chart to animate to the next key)
          • if mainSlider handler is at the end then trigger a click to emulate pause and stop the animation (playState interval)
    • $about *** When clicked goto SOCR wikipage ** .backward-skip *** when clicked decrements mainSlider’s value (which causes chart to animate to the previous key) *** when double clicked changes mainSlider’s value to it’s minimum (which causes the chart to animate to the first key) ** .forward-skip *** when clicked increments mainSlider’s value (which causes chart to animate to the next key) *** when double clicked changes mainSlider’s value to it’s maximum (which causes the chart to animate to the last key) * contextMenu ** Custom triggers are created here to control the menus per motionchart instance *** scaleCallback **** Takes a key (attached to context menu clicked), options (ignored) and mapID (maps to MapEnum) as parameters **** If mapID is passed call pass it to setScale **** Otherwise pass the button index. Therefore it is vital that the order match MapEnum and must be at the top of the context menu *** $svg:rightclick
        • Attach mapping and color items to the element data as "items" and "colorItems" respectively
        • Attach scale callback function and save callback function to the element data as "scaleCallback" and "saveCallback" respectively
        • Display the menu manually at mouse pointer
      • $svg:x.label *** $svg:y.label
      • $menu:[key|x|y|size|color|category]Map *** $menu:[x|y|size|color]Scale
      • $menu:colorColorMap
        • Attach relevant mapping items to the element data as "items"
        • Trigger context menu
  • maps
  • keyItems
  • menu
    • Initialises main interactive sliding menu event handlers
      • if menu is clicked trigger 'mouseenter' (open menu if closed)
      • if click is outside menu trigger 'mouseleave' (close menu is open)
      • On mouseenter create and populate table and append it to the menu
        • Note: All variables are substringed based on max width of the app
        • Animate (open) the menu to display the table
          • Stops any animation in progress
          • Note: priv.menuHeight can be refactored
      • On mouseleave animate (close) menu back to default (CSS) settings
  • saveAsImage
    • Save as image uses canvg to transform the SVG into a PNG image
      • Applies CSS properties inline in order to be captured by canvg
      • Call canvg with a temporary created canvas
      • Edit canvas to add title, mapping and scaling information
      • Transform canvas to PNG image data
      • Open a new window. Window title must not contain space in order to work with internet explorer.
      • Write the image data to the page
      • Remove the inline styles and update chart to restore custom colors

Chart

The chart object handles everything related to d3/SVG. This includes the axes, bubbles, text, mappings, scalings and so on.

  • init: initializes chart components
    • Creates SVG to span container
    • Create x and y axes bar and text
  • resize:
    • Called when container is being resized
    • Get new dimentions and update the SVG
    • Update the x and y axes and scales maintaining any ordinal values (which uses rangePoints as opposed to range)
    • Update the x axis label position
    • Finally remap the circles to the axes. Note that this is done directly rather than calling chart.update to ensure swift movement as resize is called frequently during resizing.
  • updateData:
    • Extracts data from handsontable using the custom (not included in the default library) function ‘getNonEmptyData’. Important note: getNonEmptyData returns the data from row 0, column 0 to the last row/column with data in them (on the row:0 column:0 axes). This function can be refined to identify a complete matrix even if pasted in the middle of the table.
    • Parses data (nest array) into CSV
    • nests data by key. In other words transforms the CSV into an associative array with (mapping) keys being its (associative) keys.
    • Creates NaNMap based on the first row of values
  • update:
    • If parameter keyIndex is passed then bind data for that particular index to nodes
    • Enter: Create and map nodes for data that aren’t mapped
    • update: Transition, with duration value of the speed slider, the nodes linearly move to their new respective x and y position while the circles (within the nodes) transition to their new radius and color.
    • exit: Remove any nodes that are not mapped to data. Let radius go to 0 before removing for a nice visual effect
    • Select all svg:text from nodes and update their text. Note only nodes that have been clicked on will have svg:text elements in them.
    • Call popover on all circles, explained in a different point.
    • Add click event to circles
      • When a node is clicked and selected append a svg:text element with category
      • if node is already selected then deselect and remove svg:text element.
      • Note the svg:text is being transitioned along with the node and, maintaining its position in the centre of the circle.
  • setPopover
    • initialize a bootstrap popover for every circle with relevant data
      • placement: Where the popover will appear. Right if node is less than ¾ the chart width and left otherwise.
      • title: Category if defined or “Data” otherwise.
      • content: display the data bind to the node.
  • updateMapping:
    • Updates individual mappings
    • Takes a key ID (which maps to MapEnum) as a parameter
    • switch (keyID)
    • MapEnum.key
      • Creates a new nest for the given key from the CSV
      • Updates chart (returns to initial)
      • Updates the main slider's maximum and resets to initial
    • MapEnum.x
      • Updates the x scale
      • if ordinal, push the values into an array and use as the domain (This is because ordinal scales take arrays as domain)
      • Apply the new scale to the x axis and reset tick formatting (Since Log scaling uses a custom different format)
      • Update x-axis Label
      • Move blobs to their new respective positions on the x-axis
    • MapEnum.y
      • Updates the y scale
      • if ordinal, push the values into an array and use as the domain (This is because ordinal scales take arrays as domain)
      • Apply the new scale to the y axis and reset tick formatting (Since Log scaling uses a custom different format)
      • Update y-axis Label
      • Move blobs to their new respective positions on the y-axis
    • MapEnum.size
      • Updates the radius scale
      • if ordinal, push the values into an array and use as the domain (This is because ordinal scales take arrays as domain)
      • Resize blobs to their new respective radius
    • MapEnum.color
      • Update the color scale
      • If ordinal, use d3's 20 color ordinal mapping, otherwise use a gradient defined by the colorPalette and color
      • Recolor the blobs to their new respective colors
    • MapEnum.category
      • Update all (if any) SVG:text attached to the nodes (blobs)
  • updateScale
    • Updates individual dimensions' scaling type
    • Takes a key ID (which maps to MapEnum) and scale ("linear","log","sqrt","polynomial") as a parameter
    • switch(keyID, toScale)
    • MapEnum.x
    • MapEnum.y
    • MapEnum.size
    • MapEnum.color
      • update xScale with the new scale type
      • Apply the scale to the xAxis
  • updateColorRange
    • Updates the chart's color gradient
    • Takes 2 parameters from and to in the format rgb(0-255,0-255,0-255)
      • Updates the colorScale and then refreshes chart.
  • isNaNMap
    • Takes an integer map ID (representing a key in MapEnum)
    • Returns a Boolean value
      • returns true if the first value of the column is NaN (not a number)
      • returns false otherwise

Design Decisions

  • Global variables: Global variables are used extensively to increase performance, this will hopefully be factored out in future revisions while maintaining performance.
  • CSV Nesting: The idea to nest and renest on given keys and values of keys (creates a nested object the given key as every key change) was chosen to provide control over the whole timeline, namely move between keys easily and efficiently. This could be replaced with the alternative "Tweening" (which interpolates at every point) in future revisions if it provides a perfomance boost.
  • The timeline/main slider was considered to be the heart of the application, it acts as an API to controlling the chart. There are only a couple places where the chart is manually updated (at initialization and remapping).

Options, Methods and Usage

How to set up the Motionchart, pass options and call methods via javascript directly is documented on https://github.com/RamyElkest/SocrMotionChartsHTML5/blob/gh-pages/README.md

Known Issues / TODO

  • Draw smaller circles on top of larger ones
  • Save data associated with selected circle
    • Open new window
    • Create handsontable
    • Get data associated with circle
    • Export data into handsontable
  • Add set circle size in right click menu (commented out).
  • Load CSV/Excel file directly to handsontable and into chart.
  • parse dates in addition to string/number
  • Refactor global variables into priv (and remove any needed)

Other features

The features below would expand the functionality of the SOCR MotionChart webapp and improve user experiences.

  • Provide a zoom-in/out/reset functionality to allow users to focus the motion chart on a smaller rectangular area (mouse-selected by click-and-drag).
  • Provide a slider for controlling the overall size of the blobs (increase as well as decrease blob relative sizes)
  • Provide a slider for controlling the opacity of the blobs (unless the blobs are tag-selected by the user). From faded blobs to fully opaque blobs).
  • Provide a meaningful mechanism for gracefully managing missing data. Adopting a missing data policy of hiding (slowly interpolating, growing, shrinking, fading, etc.) the blob as we move between known and unknown (missing) data values, independently for each location/real data.
  • Adopt an additional mechanism for Data import that allows direct access to the Worldbank data via their JSON/XML API? This would allow us access to an enormous collection of real (social, econ, Geo-spatial, environmental, etc.) data. The Google Open-Data Explorer provides a functioning example of utilization of the Worldbank data API.
  • Extend the MotionCharts to allow the Shape and Shading (appearance) models of the blobs to encode additional variables. See this D3 example that illustrates an easy way to MAP several additional data variables into the Shape characteristics. Each type of shape (e.g., clover, bean, triangle, etc.) could represent a different variable. Then the parameters of the shape (e.g., m, n1, n2, n3, a, b) can be chosen according to the values of the variable mapped to shape, reflecting the amount of deviation from the *standard* shape (identity/default values for the shape).
  • Provide the option for tracing (fading with time or line based) that enhances the visual cues of blob changes over time.

See also

References




Translate this page:

(default)
Uk flag.gif

Deutsch
De flag.gif

Español
Es flag.gif

Français
Fr flag.gif

Italiano
It flag.gif

Português
Pt flag.gif

日本語
Jp flag.gif

България
Bg flag.gif

الامارات العربية المتحدة
Ae flag.gif

Suomi
Fi flag.gif

इस भाषा में
In flag.gif

Norge
No flag.png

한국어
Kr flag.gif

中文
Cn flag.gif

繁体中文
Cn flag.gif

Русский
Ru flag.gif

Nederlands
Nl flag.gif

Ελληνικά
Gr flag.gif

Hrvatska
Hr flag.gif

Česká republika
Cz flag.gif

Danmark
Dk flag.gif

Polska
Pl flag.png

România
Ro flag.png

Sverige
Se flag.gif