Carrot Search Circles

API reference

Carrot Search Circles is a modern, in-browser, interactive visualization of hierarchical data sets. It may resemble what is known as "pie-charts" in statically rendered software but the visualization can be interactive, allowing drill-downs or adjustments at runtime.

Technically, Circles is an HTML5 application, all modern desktop and mobile browsers supporting the canvas specification will be able to display the visualization. Circles can be integrated into a HTML page using a simple JavaScript API.

Release notes with a list of bug fixes and new features are in changes.html file at the top of the distribution bundle. An up-to-date version for the latest stable release of Circles is always at: get.carrotsearch.com/circles/api.

Carrot Search HTML5 Circles is a HTML5 application that requires an up-to-date technology in the browser. Circles is checked and supported on the following browsers:

  • Chrome: two latest versions
  • Firefox: two latest versions
  • Internet Explorer: version 9 and later
  • Mac OS Safari: version 5 and later
  • iOS Safari: version 4.2 and later

The visualization code will most likely work on other modern browsers (such as Opera), though support for these is not a priority. For older browsers the previous (Adobe Flash based) version of Circles can serve as a fallback.

The entire implementation and API of the visualization is contained in the carrotsearch.circles.js file. It handles all your code's interactions with the visualization, including embedding, changing various properties, loading new data and listening to events. The entry point to the API is an instance of the CarrotSearchCircles class created during embedding.

Options

After embedding, all further interaction with the visualization is by changing options that control such aspects as the current data model, colors, font sizes and event listeners. Options can be changed using the set method as shown in the code examples.

The easiest way to start is by using the parameters demo and tuning the visual options to suit your needs. Once done, copy the option values from the Options as JavaScript panel directly to the embedding code.

Positioning

By default Circles will be positioned in the center of the container's area, with the default diameter equal to the width or height of the container (whichever value is smaller). All of these settings can be adjusted so that the visualization appears somewhere else in the container area or with a different size. This can be used to achieve effects such as a semi-circle glued to one of the container sides, for example. The following attributes are relevant for positioning: centerx, centery, diameter.

Resizing

In case the Circles HTML element changes its size, either as part of flexible layout or programmatically, the visualization needs to be resized and redrawn explicitly using the resize method. See Adjusting Size section for more information.

Data format

Input data passed to Circles need to be as a JavaScript object defining the whole hierarchy of groups and their properties. Please see the dataObject option for detailed documentation and examples.

Colors

Many options of Circles involve specifying colors, such as the global background color, colors of specific groups and labels. In all such cases, Circles accepts CSS3-compliant color strings, such as: #fff, #1d1d1d, rgba(200, 100, 0, 0.5), or hsla(120, 50%, 50%, 0.8).

Printing

Circles is a regular printable element of the page. You may want to customize the printing style sheets to hide non-relevant elements.

Embedding

Before Circles can display anything, it needs to be embedded in your HTML page. To embed Circles, your page needs to:

  1. Load carrotsearch.circles.js, which contains Circles implementation. You will usually use a dedicated script tag for this, but you can also concatenate Circles code with some other JavaScript on your page to speed up page loading.
  2. Define the HTML element that will contain Circles visualization. Circles will occupy the full width and height of the element.
  3. Inspect the static supported property of the visualization class to make sure the browser supports Circles.
  4. Initialize Circles by calling the CarrotSearchCircles constructor and providing the options object as a parameter. The only required option during initialization is the id or the element option that need to point to the HTML element you defined earlier. In most cases, you will be providing other options during initialization as well, such as the dataObject to display or some visual customizations. See the options reference for a complete list of options.

Heads up!

The embedding element should have non-zero dimensions at the time of Circles initializes. In most cases it is enough to initialize Circles once the DOM is ready. In certain cases, however, especially when the page is accessed through the file:// protocol, the element will receive its size a bit later. To avoid race conditions, you can initialize Circles in the onload event (or poll the element's dimensions periodically and call resize).

<script>
window.addEventListener("load", function() {
  // Perform Circles embedding here
});
</script>

The following example shows typical embedding code with a minimal data model. This will also be our baseline for examples where the embedding isn't explicit.

if (CarrotSearchCircles.supported) {
  var circles = new CarrotSearchCircles({
    id: "visualization",
    dataObject: {
      groups: [
        { label:"Group 1", groups: [
          { label:"Group 1.1" },
          { label:"Group 1.2" }
        ]},
        { label:"Group 2", groups: [
          { label:"Group 2.1" },
          { label:"Group 2.2" }
        ]}
      ]
    }
  });
} else {
  console.log("Visualization not supported.");
}
            

The reference to the returned instance (the circles variable) can be used to load new data model or change settings of the visualization. The instance is ready to handle method calls as soon as the constructor invocation returns. The following example sets the background color to fully opaque white. See the options reference for a list of available methods and options.

circles.set({
  backgroundColor: "rgba(255,255,255,1)"
});
            

Data model

A data model can be loaded, optionally replacing the current model, by passing the dataObject parameter either during embedding or by calling the set method, as in the example below.

circles.set({
  dataObject: {
    groups: [
      { label:"Group 1", groups: [
        { label:"Group 1.1" },
        { label:"Group 1.2" },
        { label:"Group 1.3" }
      ]},
      { label:"Group 2", groups: [
        { label:"Group 2.1" },
        { label:"Group 2.2" }
      ]},
      { label:"Group 3" }
    ]
  }
});
            

Data models not in the format presented above need to be converted prior to visualization. In this example we will load JSON data dynamically and convert it to Circles format. We use jQuery to make Ajax requests, but any other library should work equally well. Note how the model changes after a short pause (fetching model data dynamically, network connection is required).

circles.set({dataObject: {groups: [
  {}, {label: "loading..." }, {},
  {}, {label: "Please wait" }, {}
]}});

$.ajax({
  url: "../demos/assets/data/census-raw.json",
  dataType: "json",
  success: function(data) {
    var groups = [ ];
    var states = data.groups;
    var pow = 0.7;
    for (var i = 0; i < states.length; i++) {
      var s = states[i];
      groups.push({
        label: s.Placename,
        weight: s.Pop,
        groups: [
          // Ethnic data is very varied, we rise it to the power of 0.8 to 
          // make the differences less pronounced.
          { label: "Hispanic", weight: Math.pow(s.PctHisp, pow) },
          { label: "White",    weight: Math.pow(s.PctNonHispWhite, pow) },
          { label: "Black",    weight: Math.pow(s.PctBlack, pow) },
          { label: "Indian",   weight: Math.pow(s.PctAmInd, pow) },
          { label: "Asian",    weight: Math.pow(s.PctAsian, pow) },
          { label: "Hawaiian", weight: Math.pow(s.PctNatHawOth, pow) }
        ]
      })
    }

    circles.set("dataObject", { open: true, groups: groups });
  }
});
            

To clear the visualization model load a null dataObject. The example below sets a pullback animation for the existing model and then clears it after 2 seconds. Note the sequence of two parameter calls: changes to pullbackAnimation and pullbackTime need to be applied to the previous model (not the null model).

window.setTimeout(function() {
  circles.set({
    pullbackAnimation: "explode",
    pullbackTime: 2
  });
  circles.set({
    dataObject: null
  });
}, 1000);
            

Adjusting size

If the visualization container element changes size the visualization will be scaled proportionally. To redraw it from scratch taking the new size into consideration use the code that periodically polls the container's dimensions and updates the visualization accordingly. Or attach a resize event listener to the window object as shown below.

window.addEventListener("resize", function() {
  circles.resize();
});
            

Compare the behavior of visualization with and without the resize listener (resize the browser window once the example is visible).

// With the resize listener installed.
circles.set("onLayout", function(attrs) {
  console.log("Layout.", attrs);
});
                
// Without the resize listener.
circles.set("onLayout", function(attrs) {
  console.log("Layout.", attrs);
});
                

On mobile devices one should resize and redraw the visualization after the orientation of a mobile device changes:

window.addEventListener("orientationchange", function() {
  circles.resize();
});
            

Resizing and redrawing may be costly. You may consider throttling calls to resize using a delayed timer function or deferring label redrawing using deferLabelRedraws attribute.

Visualization events

Event callbacks are functions invoked by the visualization code when certain conditions or triggers are met. Some of these callbacks are invoked only for user-related actions (for example onGroupZoom), others may be a result of internal state changes (like onRolloutComplete).

Starting with version 2.2.0 all event callback attributes can be either a single function reference or an array of functions to be invoked. For reasons of backwards compatibility a function or array of callback functions will replace any existing callbacks. To attach a new function to the set of existing ones get the array of existing callbacks first, as the example below shows.

// Set the first callback, replacing any existing ones.
circles.set("onGroupHover", function(hover) {
  console.log("onGroupHover (callback 1)");
});

// Attach another callback to the existing set.
circles.set("onGroupHover", circles.get("onGroupHover").concat(function(hover) {
  console.log("onGroupHover (callback 2)");
}));
            

Color models

The default way of assigning colors to groups and labels in a model is the "rainbow" color model. This model works by first assigning colors from the provided range of hue values to first-level groups. The color assigned to the first group is taken from rainbowStartColor, the last group's color will be rainbowEndColor and all groups in between will be assigned a hue value in proportion to the weights of preceding groups.

The example below shows a set of groups covering a full spectrum of hues (angles between 0 and 360).

circles.set({
  backgroundColor: "#fff",
  visibleGroupCount: 0,
  rainbowStartColor: "hsla(  0, 100%, 50%, 1)",
  rainbowEndColor:   "hsla(360, 100%, 50%, 1)",
  groupColorDecorator: function(opts, params, vars) {
    params.group.label = "Hue: " + Math.round(vars.groupColor.h);
  },
  dataObject: {
    groups: (function() {
      var arr = [];
      for (var i = 0; i < 100; i++) {
        arr.push({label: ""});
      }
      return arr;
    })()
  }
});
            

Children groups are recursively assigned with colors that introduce a gradient to the saturation and lightness components of their parent's color, as demonstrated in the example below, which shows HSL color values assigned to each group.

function modelGen(subgroups) {
  var arr = [];
  for (var i = subgroups.pop(); i > 0; i--) {
    arr.push({label: ""});
  }
  if (subgroups.length > 0) {
    for (var i = arr.length; --i >= 0;) {
      arr[i].groups = modelGen(subgroups.slice());
    }
  }
  return arr;
}

circles.set({
  backgroundColor: "#fff",
  visibleGroupCount: 0,
  rainbowStartColor: "hsla(  0, 100%, 50%, 1)",
  rainbowEndColor:   "hsla(360, 100%, 50%, 1)",
  groupColorDecorator: function(opts, params, vars) {
    params.group.label = "" +
              Math.round(vars.groupColor.h) + ", " +
              Math.round(vars.groupColor.s) + "%, " +
              Math.round(vars.groupColor.l) + "%";
  },
  dataObject: modelGen([3, 5, 10, 1])[0]
});
            

If the default color model does not suit your needs you can customize group colors to your liking by using the groupColorDecorator callback.

Debugging

By default the visualization code does not verify whether input options are valid, this is done to minimize the size of the code required at runtime. During development, however, it is strongly recommended to turn on assertions by including carrotsearch.circles.asserts.js file. This automatically enables assertion checking for attributes passed to the visualization via the set method.

The example below shows invalid values of certain options and how they result in console warnings.

circles.set({
  id: undefined,
  backgroundColor: "foo",
  diameter: "95px",
  onLayout: "fooBar",
  imageData: "newImage.png",
  showZeroWeightGroups: "true",
  rolloutAnimation: "invalid-value",
  visibleGroupCount: 1.5,
  angleStart: -10
});
            

Carrot2 data models

If you are using Carrot2 or Lingo3G clustering engine, you can convert the JSON and XML data formats using the following jQuery snippet (for simplicity, a similar conversion could be done manually of course).

// Convert clusters to groups from Carrot2/Lingo3G JSON format.
function convert(clusters) {
  return clusters.map(function(cluster) {
    return {
      id:     cluster.id,
      label:  cluster.phrases.join(", "),
      weight: cluster.attributes && cluster.attributes["other-topics"] ? 0 : cluster.size,
      groups: cluster.clusters ? convert(cluster.clusters) : []
    }
  });
};

$.ajax({
  url: "../demos/assets/data/carrot2/ben-and-jerry.json",
  dataType: "json",
  success: function(data) {
    new CarrotSearchCircles({
      id: "visualization",
      dataObject: {
        groups: convert(data.clusters)
      }
    });
  }
});
            

And a similar conversion from the XML format.

// Convert clusters to groups from Carrot2/Lingo3G XML format.
function convert(clusters) {
  return $(clusters).map(function(index, cluster) {
    cluster = $(cluster).detach(); // destroys the model, simplifies processing.
    var subgroups = cluster.children("group").size() > 0 ? convert(cluster.children("group")) : [];
    return {
      id:     cluster.attr("id"),
      label:  cluster.find("group > title > phrase").map(asText).toArray().join(", "),
      weight: cluster.attr("score") ? cluster.attr("score") : 0,
      groups: subgroups
    }
  }).toArray();
};

// Convert a node to text.
function asText(i, e) { return $(e).text(); }

$.ajax({
  url: "../demos/assets/data/carrot2/ben-and-jerry.xml",
  dataType: "xml",
  success: function(data) {
    new CarrotSearchCircles({
      id: "visualization",
      dataObject: {
        groups: convert($(data).find("searchresult > group"))
      }
    });
  }
});
            

get(*)

get()

Returns an object containing current values of all options. Properties of the returned object correspond to option names, values are option values.

console.log(circles.get());
                  
get(option)

Returns the current value of the requested option. If the provided string does not correspond to any option name, the result is undefined.

console.log(circles.get("dataObject"));
                  

set(*)

set(option, value)

Sets the provided option to the desired value. If the provided option string does not correspond to any option, the call is ignored.

circles.set("backgroundColor", "rgba(255,255,255,1)");
                  
set(options)

Sets new values for all options included in the provided options object. Properties of the object should correspond to attribute names, values of the object will be treated as values to be set. Any properties of the options object that do not correspond to any attributes of the visualization will be ignored.

circles.set({
  backgroundColor: "#fff",
  centery: "25%",
  angleWidth: 180
});
                  
Heads up!

Setting certain options may enforce label redrawing or even reloading the full model. This may be expensive. Try to aggregate all options in one call to set. However, this may not always be possible, especially when a new data model is also set in one call. For example setting a pullback animation for the currently loaded model needs to be done before a new model is loaded to be effective, as shown in this snippet.

// We want the explode animation lasting 2 seconds so we need
// to apply these options to the current model.
circles.set({
  pullbackAnimation: "explode",
  pullbackTime: 2
});

// And then we can load a new model and apply the remaining options.
circles.set({
  rolloutAnimation: "rollout",
  rolloutTime: 2,
  dataObject: {
    groups: [
      { label:"Group 1" }, { label:"Group 2" },
      { label:"Group 3" }, { label:"Group 4" }
    ]
  }
});
                

redraw()

redraw()

Triggers a complete redraw of the visualization. This may be a costly operation depending on the complexity of the model and current settings (label redrawing).

resize()

resize()

If the size of the HTML container element has changed, resizes and redraws the visualization to accommodate to the new size. See Adjusting Size section for code snippets related to this function.

layout()

layout()

Performs full layout of the component, including redrawing of text labels. It is preferable not to use this method directly and rely calls to resize instead.

See layout attribute for read-only access to the most recent layout state if on-changed updates are not needed.

update()
@since 2.3.1

Updates the visualization to reflect changes in group weights. If you change the values of the weight property of some group, you need to call this method to see the updated distribution of circle slices.

The updateTime attribute controls the speed (in seconds) of the transition.

Calling update does not redraw group labels.

Hierarchy changes, such as adding or removing groups, cannot be performed using this method. The only way to visualize an updated hierarchy is to set a new value for the dataObject option. A partial workaround to temporarily "hide" certain groups is to set their weight to zero in combination with showZeroWeightGroups set to false, as the following example shows.

var changeWeights = function() {
  circles.get("dataObject").groups.forEach(function(e) {
    e.weight = Math.round(Math.random() < 0.2 ? 0 : Math.random() * 100);
  });
  circles.update();

  var t = window.setTimeout(changeWeights, 1500);
  window['_animHookId'] = t;
};

circles.set({
  "visibleGroupCount": 0,
  "showZeroWeightGroups": false,
  "updateTime": 1,
  "isGroupVisible": function(group) {
    return !group.gap;
  },
  "onRolloutComplete": changeWeights,
  "dataObject": {
    "groups": [
      { "label": "Group 1"}, { "gap": true },
      { "label": "Group 2"}, { "gap": true },
      { "label": "Group 3"}, { "gap": true },
      { "label": "Group 4"}, { "gap": true }
    ]
  }
});
                

groupShape(gid)
@since 2.2.0

Retrieves the information about the shape of a group with the id attribute equal to gid. The returned object can be undefined, but in general will be a hash with the following properties:

x
The group arc's horizontal center reference.
y
The group arc's vertical center reference.
angle_from
The starting angle for the group's circle slice (radians).
angle_to
The end angle for the group's circle slice (radians).
r_inner
Inner radius of the group's slice.
r_outer
Outer radius of the group's slice.

The properties above are sufficient to locate the group on screen. In combination with the onRedraw callback this can be used to create special effects like overlays on groups, as shown in this demo snippet.

The example below tracks the reference x and y position for group 2 (note how these variables are changing during the rollout transition, even though the visualization's center is constant).

circles.set({
  rolloutTime: 2,
  rolloutAnimation: "implode",
  dataObject: {
    groups: [
      { label: "Group 1", id: 1 }, { label:"Group 2", id: 2 },
      { label: "Group 3", id: 3 }, { label:"Group 4", id: 4 }
    ]
  },
  onRedraw: function() {
    var shape = circles.groupShape(2);
    if (shape) {
      console.log("X: " + shape.x + ", Y: " + shape.y);
    }
  }
});
              

onModelChanged

Called after the visualization has parsed the new data model provided in the dataObject option, but before the new data has been rendered. Callback functions receive one argument newDataObject, it is the new data object the visualization is about to show.

In the context of each callback, this points to the involved instance of CarrotSearchCircles.

// Attach a pair of listeners.
circles.set("onModelChanged", [
  function(newDataObject) {
    alert("Model changed.", newDataObject);
  },
  function(newDataObject) {
    console.log("Model changed.", newDataObject);
  }]
);

// Set a new data model to verify it works.
circles.set("dataObject", { groups: [{ label:"Group 1" }]});

// Dump the number of listeners to the console.
console.log("Listeners: " + circles.get("onModelChanged").length);
              

onRolloutStart

Called just before the visualization starts the animated rollout. If the application implements some sort of loading indicator, the indicator should be hidden once this event is fired.

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

onRolloutComplete

Called after the rollout animation has completed.

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

onRedraw

Called after the internal- or API-triggered redraw of the visualization.

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

Any code in this callback should be minimal and ultra fast to keep the transition animations smooth.

var redraws = 0;
circles.set({
  rolloutTime: 2,
  pullbackTime: 2,
  pullbackAnimation: "explode",
  rolloutAnimation: "rollout",
  onRedraw: function() {
    redraws++;
  },
  onRolloutComplete: function() {
    console.log("Rollout complete, redraws: " + redraws);
  }
});
// Force model reload to get some redraws.
var m = circles.get("dataObject");
circles.set("dataObject", null);
circles.set("dataObject", m);
              

onLayout

Called after the internal- or API-triggered layout of the visualization. The callback function receives a single layoutData object with several properties that describe the shape of the visualization on screen. This can be used to position additional custom components, for example (such as the selected group's title).

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

circles.set("onLayout", function(layoutData) {
  console.log("Layout data.", layoutData);
});
circles.layout();
              

onGroupHover

Called after the mouse pointer enters the area covered by a group or leaves the visualization area completely. The callback will be passed one parameter: an object containing the group property with a reference to the data model object representing the group the cursor hovers above or null if the mouse pointer is not over any group.

To not display the hover outline, set groupHoverColor and groupHoverOutlineColor to a transparent color.

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

circles.set("onGroupHover", function(hover) {
  console.log("onGroupHover.", hover.group ? hover.group.label : "(none)");
});
              

onGroupOpenOrClose

Called after a collapsed group has been opened or after an expanded group was closed. This event is only emitted if expanders are shown and if the number of children groups exceeds the threshold set in visibleGroupCount option. For each involved group, the callback function will be invoked with one parameter containing the following properties.

group
Reference to the data object representing the group that has just been opened or closed.
open
true if the group has just been opened for browsing of child groups, false if the group has just been closed

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

The callback is not invoked for API-initiated open state changes, only for user interactions.

circles.set({
  visibleGroupCount: 1,
  onGroupOpenOrClose: function(info) {
    console.log("onGroupOpenOrClose.", info);
  }
});
console.log("Expand/contract closed groups to invoke the callback.");
              

onGroupZoom

Called after a group has been zoomed in or its zoom status was reset back to normal. For each involved group, the callback will be invoked with one parameter containing the following properties:

group
Reference to the data object which is subject to zoom state change.
zoomed
true if the group has just been zoomed, false otherwise

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

The callback is not invoked for API-initiated state changes, only for user interactions.

circles.set({
  onGroupZoom: function(info) {
    console.log("onGroupZoom.", info);
  }
});

// Zoom-in group with identifier '1.1' programmatically.
circles.set("zoom", "1.1");
console.log("Double click or tap-and-hold on the zooomed group.");
              

onGroupSelectionChanging

Called during group selection changes. For each selected or deselected group, the callback will be invoked with one parameter containing the following properties:

group
Reference to the data object which is subject to selection state change.
selected
true if the group has just been selected, false otherwise

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

The callback is not invoked for API-initiated state changes, only for user interactions.

When multiple callbacks are present the entire set will be invoked, even if one or more callbacks changes the selection via an API call.

Note that one user action may result in many events (deselection of currently selected groups and selection of another group, for example). A global selection state is easier to track using onGroupSelectionChanged event.

circles.set({
  onGroupSelectionChanging: function(info) {
    console.log("onGroupSelectionChanging.", info);
  },
  onGroupSelectionChanged: function(info) {
    console.log("onGroupSelectionChanged.", info);
  }
});

console.log("Select and deselect groups.");
              

onGroupSelectionChanged

Called once after the selection has changed. The callback receives one parameter: an object containing the groups property with an array of references to the data objects representing the groups that are currently selected.

For tracking selection changes on individual groups, onGroupSelectionChanging event should be used. See code snippet there for an example.

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

The callback is not invoked for API-initiated state changes, only for user interactions.

When multiple callbacks are present the entire set will be invoked, even if one or more callbacks changes the selection via an API call.

onBeforeSelection

Called right after some user interaction which would affect the model's selection (mouse click on a group for example), but before the changes are applied. The callback will be invoked with one object parameter containing a group property with the data model group affected.

If any callback function returns false, the default behavior of adjusting selection is not invoked. This can be used to prevent the display of selection entirely, as in this example.

circles.set("onBeforeSelection", [
  function() { return true; },
  function() { return false; }
]);
              

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

The callback is not invoked for API-initiated state changes, only for user interactions.

onBeforeZoom

Called right after some user interaction which would affect the model's zoom state, but before the changes are applied. The callbacks will be invoked with one object parameter containing a group property with the data model group affected.

If any callback function returns false, the default behavior of adjusting zoom state is not invoked. This can be used to prevent the feature of zooming groups entirely, as in this example.

circles.set("onBeforeZoom", function() { return false });
              

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

The callback is not invoked for API-initiated state changes, only for user interactions.

onGroupClick

Called after a mouse click (or tap gesture) was detected on a group. The callback will be invoked with an object containing the following properties:

group
property with the data model group affected.
metaKey
A copy of the original event's metaKey. Note that this key modifier is known not to work in Windows-based browsers.
ctrlKey
A copy of the original event's ctrlKey.
shiftKey
A copy of the original event's shiftKey.

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

The callback is not invoked for API-initiated state changes, only for user interactions.

circles.set("onGroupClick", function(info) {
  console.log("onGroupClick.", info);
});
              

onGroupDoubleClick

Called after a double click (or tap-and-hold gesture) was detected on a group. The callback will be invoked with an object containing the following properties:

group
property with the data model group affected.
metaKey
A copy of the original event's metaKey. Note that this key modifier is known not to work in Windows-based browsers.
ctrlKey
A copy of the original event's ctrlKey.
shiftKey
A copy of the original event's shiftKey.
// Prevent the default behavior and toggle selection on double click.
circles.set({
  onBeforeZoom:       function() { return false; },
  onBeforeSelection:  function() { return false; },
  onGroupDoubleClick: function(info) {
    var state = info.group.selected ? true : false;
    this.set("selection", {
      groups: [info.group.id],
      selected: !state
    });
    console.log((state ? "Deselected" : "Selected") + " group " + info.group.id);
  }
});
              

In the context of the callback, this points to the involved instance of CarrotSearchCircles.

The callback is not invoked for API-initiated state changes, only for user interactions.

General

supported

A static property on CarrotSearchCircles equal to true if visualization is supported on the current browser environment.

Heads up!

This property is static and must be accessed directly on CarrotSearchCircles class, as shown in this example:

  if (CarrotSearchCircles.supported) {
    // Put embedding code here.
    console.log("Visualization supported.");
  } else {
    // Display a warning or skip visualization.
    console.log("Visualization not supported.");
  }
                  

id

Identifier of the DOM element into which the visualization is to be embedded.

Heads up!

The DOM element should have the target (non-zero) width and height before embedding. The visualization will allocate a canvas of exactly the same size (client size) and will stretch proportionally when the element's size changes. The visualization must be resized manually (see resize method) to update to new element's dimensions.

element

The DOM element into which the visualization is to be embedded. Please see the id option for additional considerations.

dataObject

The data model to visualize in JSON format. The top-level object is a root group and must contain a non-empty groups property pointing to an array of objects representing individual first-level groups (this array can be empty). An null model object clears the visualization (removes any current model).

Each group can contain the following properties:

id
(optional, String) unique identifier of the group. Group identifiers are required only for programmatic changes of certain group attributes such as selection, zoom state or open state.
label
(required, String) textual description of the group. For best results, use short labels. groupLabelDecorator callback can also be used to modify labels before they are drawn on the visualization when the model is retrieved from a source that cannot be altered.
weight

(optional, Number >= 0) weight of the group relative to other groups. The larger the weight, the more space the group's polygon will occupy on the screen. Good values for the weight property could be e.g. the number of documents in a cluster or the score of the cluster.

Group weights must be non-negative. Zero-weight groups can receive special treatment, see the showZeroWeightGroups option.

If a group's weight is not specified, Circles will assume the weight is 1.0.

groups
(optional, Array) an array of subgroups of the group.
open
(optional, boolean) if true, all of this group's subgroups will be shown initially. By default a maximum of visibleGroupCount subgroups is shown and then the user needs to click on an expander symbol to open up a group.
zoomed
(optional, boolean) if true, the group is in zoomed state by default. Zoomed groups take proportionally larger amount of space of the parent group's circle slice (see zoomedFraction parameter).
selected
(optional, boolean) if true, the group will be initially in selected state. This can be useful to visually highlight a certain group (or groups) as the model is loaded.

The data object (and groups) can contain user properties, not required or interpreted by the visualization. These properties may be used in callback hooks (for example for color model tuning). The following example shows a data object with various values of the above properties.

circles.set({
  visibleGroupCount: 2,       // display an expander after the first 2 groups.
  dataObject: {
    open: false,              // keep the the top-level group closed.
    groups: [
      { label: "Bad news", groups: [
        { label: "Last penny lost", sentiment: -0.5 },
        { label: "Bazinga doomed",  sentiment: -1 }
        ]},
      { label: "Good news", zoomed: true, groups: [
        { label: "iPads under $100",      sentiment: 0.5, selected: true },
        { label: "Martians are friendly", sentiment: 1 }
        ]},
      { label: "Other news", groups: [
        { label: "Vampires on the loose", sentiment: -2 },
        { label: "Van Helsing to the rescue", sentiment: -3 }
        ]}
    ]
  }
});
                

See this section for hints on how to convert data models that are not in the required format.

pixelRatio

The physical-to-display pixel count ratio to assume when drawing the final visualization. On modern devices with high-density screens (such as the Retina display) one logical pixel can be mapped to more than one physical pixel. You can use this option to increase the resolution at which the visualization is drawn to make the image clearer and labels more legible. You can also decrease this resolution to make rendering faster at the cost of quality.

By default, pixelRatio is 1. In such cases, the width and height of the canvas on which the visualization is drawn will be equal to the pixel dimensions of the HTML container element. For pixelRatio values greater/smaller than 1, the pixel dimensions of the canvas will be smaller/larger than the dimensions of the enclosing HTML element and the canvas will be stretched to fit in the container's client area. For example, if you set pixelRatio to 2 and if the size of the enclosing HTML element is 400x400 pixels, the size of the canvas will be 800x800 pixels. Such a configuration will better utilize the extra pixels available on Retina displays, for example.

On most modern browsers you can retrieve the device-specific pixel ratio from the window.devicePixelRatio property.

The following examples demonstrate the difference between a pixel ratio higher and lower than the pixel resolution. Note the performance drop when pixel ratio is set to a large value.

circles.set({
  dataObject: largeDataSet,
  titleBar: "inscribed",
  pixelRatio: 0.5
});
                      
circles.set({
  dataObject: largeDataSet,
  titleBar: "inscribed",
  pixelRatio: 1
});
                      
circles.set({
  dataObject: largeDataSet,
  titleBar: "inscribed",
  pixelRatio: 2
});
                      
Heads up!

To boost the performance on iPads with Retina display set pixelRatio to 2 and the width of the HTML container element to be at least one pixel less than the total screen width. Yes, we know it's weird but it speeds up rendering a lot.

When setting pixelRatio to a value larger than 1 on mobile devices, make sure to correctly set the size of the page's viewport. The default viewport may be very large, leading to a very large canvas. Refer to the mobile demo's source code for examples.

captureMouseEvents

The visualization by default captures all mouse events and prevents them from bubbling up the DOM hierarchy. This helps avoid interactions with system features such as scrolling or zooming on touch-based devices. In scenarios where such features should be available to the user this option should be set to false.

Let's capture all mouse events happening on the body object (a parent of the visualization example in the DOM hierarchy).

var hook = window["_loggingHook"] = function(evt) {
  console.log("Mouse event: " + evt.type);
};
$("body").on("mousedown mouseup", hook);
                

Compare the following two visualization setups (hover over the visualization and note events printed to the console when mouse buttons are pressed or released).

circles.set({
  captureMouseEvents: false
});
                      
circles.set({
  captureMouseEvents: true
});
                      

Appearance

backgroundColor

The background color for the canvas behind the visualization. Visualizations with fully opaque or fully-transparent backgrounds may be slightly faster to draw. See Colors section for color specifications.

circles.set("backgroundColor", "#fff");
circles.set("backgroundColor", "rgba(0,0,255,.2)");
circles.set("backgroundColor", "hsla(120, 50%, 50%, 0.8)");

centerx

Horizontal position of the big circle's center. The position can be expressed in any of the following ways:

  • a percentage (string) of available container width, where '50%' indicates the center of the container,
  • an absolute value (number) interpreted as pixels from the left side of the container,
  • a function accepting an object ({width: _, height: _ } and returning a number interpreted as above.
circles.set("centerx", "25%");
circles.set("centerx", 20);

circles.set({
  centerx: function(box) {
    return box.width * 0.75;
  }});
                    

centery

Vertical position of the big circle's center. The position can be expressed in any of the following ways:

  • a percentage (string) of available container height, where '50%' indicates the center of the container,
  • an absolute value (number) interpreted as pixels from the top side of the container,
  • a function accepting an object ({width: _, height: _ } and returning a number interpreted as above.
circles.set("centery", "25%");
circles.set("centery", 20);
circles.set({
  centery: function(box) {
    return box.height * 0.75;
  }});
                    

layout

Returns the most recent layout state. The format is identical to the object passed to the callback function in onLayout method. The returned value may be undefined if no model is displayed or if the layout data is not yet available.

console.log("Current layout.", circles.get("layout"));
                

diameter

Diameter of the big circle (outer ring). The diameter can be expressed in any of the following ways:

  • a percentage (string) of available container width or height (whichever is smaller),
  • an absolute value (number) interpreted as pixels,
  • a function accepting an object ({width: _, height: _ } and returning a number interpreted as above.
circles.set("diameter", "50%");
circles.set("diameter", 400);
circles.set({
  diameter: function(box) {
    return Math.max(box.height, box.width);
  }});
                    

ringScaling

Child ring sizing multiplier. Each ring's size will be the size of its parent, multiplied by ringScaling. The center space counts as an "invisible" ring in these calculations.

circles.set("ringScaling", 0.5);
circles.set("ringScaling", 1);
circles.set("ringScaling", 1.5);

See ringShape attribute for defining custom ring shapes.

ringShape

While ringScaling gives gives pretty good control over ring sizes, sometimes a more fine-grained control is needed. The ringShape attribute lets one use a callback function to modify each group's inner and outer ring properties (and possibly other ring shape properties in the future). The callback function receives an associative array with the following properties.

group
The group for which the ring size should be calculated.
maxRadius
A hint about the maximum radius of the circle. This value can but needs not to be respected.
centerx
The current horizontal center of the circle.
centery
The current vertical center of the circle.
r_inner
The inner radius for the group, as calculated by the default algorithm.
r_outer
The outer radius for the group, as calculated by the default algorithm.

The callback function is expected to modify (or leave unchanged) r_inner and r_outer properties. Any return value returned by the callback is ignored. Model groups are traversed in-depth, in post-order (parent first).

The callback function is called once per layout call for performance reasons.

A combination of ringShape and isGroupVisible callbacks can be used to create a "wedge" effect, as the following example shows.

circles.set({
  visibleGroupCount: 0,
  isGroupVisible: function(group) { return !group.gap; },
  ringShape: function(attrs) {
    if (attrs.group.wedge) {
      attrs.r_inner -= 40;
      attrs.r_outer += 40;
    }
  },
  dataObject: { groups: [
    {label: "1"},
    {label: "2"},
    {gap: true, weight: 0.1},
    {label: "3", wedge: true},
    {gap: true, weight: 0.1},
    {label: "4"},
    {label: "5"}
  ]}
});
                

More complex customizations are of course possible. The following example stacks up groups on top of one another, creating a spiky shape that lies pretty far from visualization's defaults.

// Link up groups with their parents. We will need this
// in the callback hook. Calculate max depth as we go.
var model = dataSets["full-3-level.jsonp"].data;
var depth = 0;
var assignParent = function(parent, level) {
  depth = Math.max(level, depth);
  parent.groups && parent.groups.forEach(function(e) {
    e.parent = parent;
    assignParent(e, level + 1);
  });
};
assignParent(model, 0);

var variance = 30;  // Randomize each group's ring by this value.
circles.set({
  dataObject: model,
  rolloutTime: 2,
  rolloutAnimation: "implode",
  visibleGroupCount: 8,
  // Decrease the diameter by max. ring size variation
  diameter: function(box) {
    return Math.min(box.height, box.width) - 2 * variance * depth;
  },
  // Calculate ring sizes varying them randomly and stacking each
  // on top of its parent (relies on depth-first preorder traversal).
  ringShape: function(attrs) {
    var group = attrs.group;
    if (group.parent) {
      var r_width = attrs.r_outer - attrs.r_inner;
      group.r_inner = attrs.r_inner = group.parent.r_outer;
      group.r_outer = attrs.r_outer = group.r_inner + r_width + Math.random() * variance;
    } else {
      group.r_inner = attrs.r_inner;
      group.r_outer = attrs.r_outer;
    }
  }
});
                

angleStart

Start angle of the big circle's first group. The angles are expressed in degrees, clockwise, starting from zero angle on the right, as shown in the illustration below.

The following examples show groups laid out in semi-circles. Note the direction indicated by angleWidth.

circles.set({
  angleStart: 0,
  angleWidth: 180
});
                    
circles.set({
  angleStart: 180,
  angleWidth: -180
});
                    

angleWidth

Maximum angular "width" assigned for all top-level groups. A full clockwise circle will have a width of 360, a counterclockwise layout will have a width of -360. The following example animates both angleStart and angleWidth

var angleStart = 0;
var angleWidth = 0;
var hook = function() {
  angleStart = (angleStart + 10) % 360;
  angleWidth = (angleWidth + 15) % (360 * 2);
  circles.set({
    angleStart: angleStart,
    angleWidth: angleWidth - 360
  });
  window['_animHookId'] = window.setTimeout(hook, 200);
};
hook();
                

angleEnd

An attribute providing the end angle of the big circle's first level groups. When this attribute is provided the groups will always start at angleStart and end at angleEnd, always clockwise. If angleEnd is smaller than angleStart their values are swapped. For this reason it is not possible to render certain shapes with angleEnd, for instance display groups in counter-clockwise order. This constraint is lifted when angleWidth is used. Compare the following examples and their group order.

circles.set({
  angleStart: 30,
  angleEnd:   330
});
                    
circles.set({
  angleStart: 330,
  angleEnd:   30
});
                    
circles.set({
  angleStart: 330,
  angleWidth: -300
});
                    
Heads up!

This attribute is deprecated and is kept for backwards compatibility only. It is highly recommended that new code uses angleWidth which is more flexible and allows creating layouts impossible with angleEnd.

showZeroWeightGroups

If true, groups whose weight is zero will be visible, with their weight slightly smaller than the smallest non-zero weight of the sibling groups.

circles.set({
  showZeroWeightGroups: true,
  dataObject: {
    groups: [
      { label: "Group 1", weight: 1 },
      { label: "Group 2", weight: 1 },
      { label: "Group 3", weight: 1 },
      { label: "Group 4", weight: 0 },
    ]
  },
});
                    
circles.set({
  showZeroWeightGroups: false,
  dataObject: {
    groups: [
      { label: "Group 1", weight: 1 },
      { label: "Group 2", weight: 1 },
      { label: "Group 3", weight: 1 },
      { label: "Group 4", weight: 0 },
    ]
  },
});
                    

visibleGroupCount

Maximum number of groups to display in a sub-level of a parent group. If there are more children than visibleGroupCount, an expander widget is shown, allowing the user to open up a group manually. A visibleGroupCount equal to zero means all children groups will be displayed.

circles.set({
  visibleGroupCount: 2,
  dataObject: {
    groups: [
      {label: "G1"}, {label: "G2"},
      {label: "G3"}, {label: "G4"}
    ]
  },
});
                    
circles.set({
  visibleGroupCount: 4,
  dataObject: {
    groups: [
      {label: "G1"}, {label: "G2"},
      {label: "G3"}, {label: "G4"}
    ]
  },
});
                    

zoomedFraction

For zoomed-in groups specifies the fraction of their parent group's current angle. This fraction is evenly distributed among all zoomed-in groups belonging to one parent. See the example below.

circles.set({
  zoomedFraction: 0.5,
  dataObject: {
    groups: [
      {label: "G1"}, {label: "G2"},
      {label: "G3"},
      {label: "G4", zoomed: true}
    ]
  },
});
                    
circles.set({
  zoomedFraction: 0.5,
  dataObject: {
    groups: [
      {label: "G1"}, {label: "G2"},
      {label: "G3", zoomed: true},
      {label: "G4", zoomed: true}
    ]
  },
});
                    

isGroupVisible

A callback used to determine whether a given group should be displayed or not. The callback function will receive a single argument: the group object from the dataObject model.

An invisible group still occupies space in the visualization but will not be rendered and will not receive mouse events (hover, clicks). It is effectively a blank space between the surrounding siblings (if any). This function can be used to implement visual separators (spaces) between groups, as in the following example.

circles.set({
  zoomedFraction: 0.5,
  isGroupVisible: function(group) { return group.visible; },
  visibleGroupCount: 0,
  dataObject: {
    groups: (function() {
      var v = [];
      for (var i = 1; i <= 50; i++) {
        var visible = i & 1;
        v.push({label: "G" + i, visible: visible, weight: 1 / i});
      }
      return v;
    })()
  }
});
                

A more practical use for the gap groups is to insert a visual space between parent groups so that their children are separated. The following example inserts such spaces automatically to the current model.

circles.set({
  isGroupVisible: function(group) { return !group.gap; },
  dataObject: { groups: [
    {label: "1", groups: [{label: "1"}, {label: "2"}, {label: "3"}]},
    {gap: true, weight: 0.1},
    {label: "2", groups: [{label: "1"}, {label: "2"}, {label: "3"}]},
    {gap: true, weight: 0.1},
    {label: "3", groups: [
      {label: "1"},
        {gap: true, weight: 0.1},
      {label: "2"},
        {gap: true, weight: 0.1},
      {label: "3"}]},
    {gap: true, weight: 0.1}
  ]}
});
                

Groups

groupOutlineWidth

Width of the outline stroke. Stroke of zero means no outline is drawn. The outline is painted with groupOutlineColor.

circles.set("groupOutlineWidth", 5);
                

groupOutlineColor

Color of the outline stroke, groupOutlineWidth controls the stroke's width.

circles.set({
  groupOutlineWidth: 5,
  groupOutlineColor: "#fff"
});
                

Note that transparent outlines will not remove the underlying group's fill (this is a technical limitation of HTML5 canvases). Unless this is the desirable effect, transparencies should be avoided.

circles.set({
  groupOutlineWidth: 10,
  groupOutlineColor: "rgba(0,0,0,0.2)"
});
                

rainbowStartColor

Start color to use if rainbow color model is used for coloring groups. A rainbow color model will proportionally spread the color hue among top-level groups (starting at rainbowStartColor and ending at rainbowEndColor). Any sub-groups will be painted with the parent group's hue but with varying degrees of saturation and lightness.

circles.set("dataObject", largeDataSet);
circles.set("open", {all: true});
                

For custom coloring groupColorDecorator should be used.

rainbowEndColor

Start color to use if rainbow color model is used for coloring groups. See rainbowStartColor for a description of how rainbow color model works.

groupColorDecorator

A callback function to customize or completely replace the default rainbow color model for groups. During each redraw, the visualization core will call the provided function once for each group. The task of the function is to modify or completely replace the default color of the group and its label. New colors can be derived from various properties of the group, such as its nesting level, number of siblings, or custom properties passed along with the data model.

The callback function receives three arguments: options, properties and variables.

options
all current visualization options (keys and values).
properties

a hash object with properties describing the current group. The object contains the following properties:

  • group: the data object corresponding to the group, exactly as passed in the dataObject option.
  • weightNormalized: the weight of the group normalized to the 0..1 range in relation the group's siblings (the raw weight of the group can be retrieved from the original data model if it was present there).
  • index: the position of the group relative to its siblings in the input data object. Position indices start at 0.
  • siblingCount: the number of immediate siblings of the group.
  • level: the nesting level of the group. Level numbers start with 0.
variables

a hash with two keys (groupColor, labelColor) and values computed by the default rainbow model. The callback function can either change some of the properties of these values or replace them with new ones.

The callback function can replace these values with a CSS3 color string (hsl, hsla, rgb or rgba color specification) or a hash object containing the following properties:

  • model: rgba for the RGBA model, hsla for the HSLA model. When adding or changing the values of other properties, it is important to set this property to reflect the desired color model.
  • r, g, b, a: RGBA values (integers in the 0..255 range).
  • h, s, l, a: HSLA values, hue is an angle (0..359), saturation and lightness are percentages (0..100) and transparency is a range between 0 and 1.

The default values computed by the rainbow color model are always passed to the callback in the HSLA hash format described above.

The labelColor variable can be set to a string value of auto which will recompute the label color again depending on the updated groupColor and visualization options controlling dark/ light label colors: labelColorThreshold, labelLightColor and labelDarkColor.

The simplest color model could just use explicit color values provided as part of the model.

var customAttributes = function(opts, params, vars) {
  console.log("Color decorator callback.", params, vars);
  vars.groupColor = params.group.gcolor;
  vars.labelColor = "auto";
};

circles.set({
  groupColorDecorator: customAttributes,
  dataObject: {
    groups: [
      { label: "C", gcolor: "#00aeef" },
      { label: "M", gcolor: "#ec008c" },
      { label: "Y", gcolor: "#fff200" },
      { label: "K", gcolor: "#231f20" }
    ]
  }
});
                

A more complex example is shown below. Here we compute HSLA values depending on sentiment attribute of each group.

var sentimentPainter = function(opts, params, vars) {
  // Sentiment is a custom property with values in the -1..+1 range, -1 means negative sentiment,
  // +1 -- positive, 0 -- neutral
  var sentiment = params.group.sentiment;
  if (!sentiment) {
    // Make neutral groups grey
    vars.groupColor.s = 0;
    vars.groupColor.l = 0;
    vars.groupColor.a = 0.2;
  } else {
    if (sentiment > 0) {
      // Make positive groups green
      vars.groupColor.h = 120;
    } else {
      // Make negative groups red
      vars.groupColor.h = 0;
    }

    // Make saturation and lightness depend on
    // the strength of the sentiment.
    vars.groupColor.s = 50 * (1 + Math.abs(sentiment));
    vars.groupColor.l = Math.abs(60 * sentiment);

    // Indicate that we use the HSL model
    vars.groupColor.model = "hsl";
  }
};

circles.set({
  groupColorDecorator: sentimentPainter,
  dataObject: {
    groups: [
      { label: "Bad news", groups: [
        { label: "Last penny lost", sentiment: -0.5 },
        { label: "Bazinga doomed",  sentiment: -1 }
        ]},
      { label: "Good news", groups: [
        { label: "iPads under $100",      sentiment: 0.5 },
        { label: "Martians are friendly", sentiment: 1 }
        ]},
      { label: "Other news", groups: [
        { label: "Vampires on the loose", sentiment: 0 },
        { label: "Van Helsing to the rescue", sentiment: 0 }
        ]}
    ]
  }
});
                

labelColorThreshold

Picks the label color automatically depending on the group's color brightness. If the brightness is below this threshold labelLightColor is used, otherwise labelDarkColor is used. Setting the threshold to either 0 or 1 will pick dark or light labels, correspondingly.

Automatic choice of label colors is also provided as part of groupColorDecorator's functionality.

labelDarkColor

The label color to use on bright groups (see labelColorThreshold).

labelLightColor

The label color to use on dark groups (see labelColorThreshold).

zoomDecorationFillColor

Color of the zoomed-in group's decoration. Note that transparency may be used to remove the decoration entirely.

circles.set({
  zoomDecorationFillColor: "rgba(0,0,0,0)",
  zoomDecorationStrokeColor: "#fff"
});
circles.set("zoom", "1");
                

zoomDecorationStrokeColor

Color of the zoomed-in group's decoration stroke. See zoomDecorationFillColor

Labels

groupFontFamily

Font family to use for drawing group labels. CSS-compliant font family specifications are supported, including webfonts imported using the @font-face syntax.

Study the source code of the mobile demo for an example of using Google web fonts.

groupMinFontSize

Minimum font size used for drawing group labels. Two font sizing strategies are supported:

  • absolute: when the property is set to a numeric value, such as 27, the value will be interpreted as the number of pixels,
  • relative: when the property is set to a percentage, such as 40%, the font size will be calculated as a percentage of the group's maximum allowed label height.

Note that setting minimum font size to a large value may result in empty labels (unlabeled groups) or very unclear labels ending in ellipsis, as in this example:

circles.set({
  dataObject: largeDataSet,
  groupMinFontSize: 30,
  groupMaxFontSize: 40
});
                

groupMaxFontSize

Maximum font size used for drawing group labels. See the groupMinFontSize attribute for details.

groupLinePadding

Padding between lines in multi-line labels, expressed in pixels. This padding may be necessary for certain fonts to avoid font trimming as there is no (fast) way to get exact rendered text metrics from within canvas/JavaScript.

groupLabelDecorator

A callback function to customize the label of each group. The function will be called on each layout() operation (which is a consequence of most calls to resize).

The callback function receives three arguments: options, properties and variables. Options and properties are identical to those passed to groupColorDecorator (see their description there). The variables argument is a hash with a single key labelText that the callback function can overwrite with a decorated value (or may leave as-is if the current label should be used).

The following example reverses all group labels and adds angle quotes around it.

circles.set({
  groupLabelDecorator: function(opts, props, vars) {
    vars.labelText = "«" + vars.labelText.split("").reverse().join("") + "»";
  }
});
                

ratioAngularPadding

How much padding space to leave on the left and right of the group's circle slice. The value is a ratio (percentage) of the entire angle assigned for the group.

This attribute is useful in combination with minAngularPadding, ratioRadialPadding and minRadialPadding as they all affect the visual padding applied to the label fit box before the label is rendered. The following figure illustrates these concepts (note the radial and angular dimensions; the grayed area within the group is the allowed fit box for the label).

minAngularPadding

Minimum angular padding for group labels. In pixels. See ratioAngularPadding for an explanation how paddings work.

ratioRadialPadding

How much padding space to leave at the top and bottom of the group's circle slice. The value is a ratio (percentage) of the entire group's height (a difference between the inner and outer radii). See ratioAngularPadding for an explanation how paddings work.

minRadialPadding

Minimum radial padding space, in pixels. See ratioAngularPadding for an explanation how paddings work.

radialTextureStep

Specifies the radial interval between "tiles" which are used to map rectangular label image onto the slice of the circle. Smaller numbers will result in higher quality.

Texture mapping is an expensive operation that requires a trade-off: more subdivision polygons will result in high quality but will render longer. When high interactivity is expected (or on slower devices) poorer textures may be a better choice instead of long delays required to render labels. In addition, noTexturingCurvature attribute allows one to define the threshold when the texture should not be applied at all (when the curvature of the label is small enough that it can be drawn as a straight box).

textureMappingMesh is helpful in debugging and estimating the needed value of this parameter. The following examples show different texture mapping settings and how they affect labels quality.

circles.set({
  dataObject: largeDataSet,
  noTexturingCurvature: 0,
  radialTextureStep: 15,
  angularTextureStep: 15,
  textureMappingMesh: true
});
                    
circles.set({
  dataObject: largeDataSet,
  noTexturingCurvature: 0,
  radialTextureStep: 30,
  angularTextureStep: 30,
  textureMappingMesh: true
});
                    
circles.set({
  dataObject: largeDataSet,
  noTexturingCurvature: 0,
  radialTextureStep: 50,
  angularTextureStep: 50,
  textureMappingMesh: true
});
                    

The following example sets noTexturingCurvature to different thresholds so that near-straight labels are not textured at all (fully grayed boxes).

circles.set({
  dataObject: largeDataSet,
  noTexturingCurvature: 0.2,
  radialTextureStep: 15,
  angularTextureStep: 15,
  textureMappingMesh: true
});
                    
circles.set({
  dataObject: largeDataSet,
  noTexturingCurvature: 0.4,
  radialTextureStep: 15,
  angularTextureStep: 15,
  textureMappingMesh: true
});
                    
circles.set({
  dataObject: largeDataSet,
  noTexturingCurvature: 0.8,
  radialTextureStep: 15,
  angularTextureStep: 15,
  textureMappingMesh: true
});
                    

angularTextureStep

Specifies the angular interval (in pixels) between "tiles" which are used to map rectangular label image onto the slice of the circle. See the description and examples accompanying radialTextureStep attribute for more.

noTexturingCurvature

Sets the threshold between normal label rendering and texture-mapping onto a slice of a circle. See the description and examples accompanying radialTextureStep attribute for more.

textureOverlapFudge

Texture mapping may result in empty gap pixels between triangles on certain browsers (due to anti-aliasing of images). This parameter controls how much triangles overlap which to some extent solves the problem. The default value of this parameter is determined depending on the current browser.

deferLabelRedraws

Defers redrawing of labels for a given number of seconds after a layout. In case any changes are made to the visualization in the mean time that would result in another layout operation, redrawing of labels will be further delayed. This is a trick to speed up interactions on slower devices.

Compare how labels are drawn on the following examples (resize the window once the example is visible).

circles.set({
  dataObject: largeDataSet,
  deferLabelRedraws: 0,
  labelRedrawFadeInTime: 0
});
                    
circles.set({
  dataObject: largeDataSet,
  deferLabelRedraws: 1,
  labelRedrawFadeInTime: 0
});
                    
circles.set({
  dataObject: largeDataSet,
  deferLabelRedraws: 1,
  labelRedrawFadeInTime: 1
});
                    

labelRedrawFadeInTime

Duration of fade-in effect for labels after they are redrawn from scratch. The value is expressed in seconds. Used in combination with deferLabelRedraws.

ratioAspectSwap

Provides control over group label's direction (angular or radial). The aspect ratio of a label is computed by dividing the width and height of the label box (see ratioAngularPadding for an explanation). If the computed value is smaller than ratioAspectSwap the label is rotated and printed along the radial direction.

This parameter can be used to enforce directionality of labels by passing extreme ratios, as in the following example.

// Only radial labels.
circles.set({
  dataObject: largeDataSet,
  ratioAspectSwap: 10000
});
                    
// Only angular labels.
circles.set({
  dataObject: largeDataSet,
  ratioAspectSwap: 0.0001
});
                    

Expanders

expanderAngle

An angle (in degrees) to reserve for an expander widget if a group has sub-groups and their number exceeds visibleGroupCount. The expander space will be either expanderAngle or half of the group's available angle, whichever value is smaller.

minExpanderAngle

If the angle assigned for a group is smaller than minExpanderAngle, the expander widget will not be displayed at all (due to lack of space).

expanderOutlineWidth

Expander widget's outline stroke width in pixels.

expanderOutlineColor

Expander widget's outline color.

expanderColor

Expander widget's fill color.

Title bar

titleBar

A title bar is a text label component inscribed into the center of the circle or overlaid at the top/ bottom of the visualization area. A title bar shows the label of the group under cursor or label(s) of currently selected groups.

The title bar was introduced in version 2.2.0 of Circles.

The following snippets demonstrate each type of the title bar component and combinations of attributes which may be handy to achieve certain effects.

circles.set({
  dataObject: largeDataSet,
  titleBar: "none"
});
                    
circles.set({
  dataObject: largeDataSet,
  titleBar: "inscribed"
});
                    
circles.set({
  dataObject: largeDataSet,
  titleBar: "topbottom",
  titleBarBackgroundColor: "rgba(0,0,0,.2)",
  titleBarTextColor: "#fff",
  titleBarTextPaddingTopBottom: 10
});
                    
var padding = 5;
var fraction = 10 / 100;
circles.set({
  dataObject: largeDataSet,
  titleBar: "bottom",
  titleBarTextColor: "#fff",
  titleBarTextPaddingTopBottom: 5,
  titleBarMinFontSize:  "5%",
  titleBarMaxFontSize:  "8%",
  diameter: function(box) {
    return (box.height - padding * 2) * (1 - fraction);
  },
  centery: function(box) {
    var h = box.height - padding - box.height * fraction;
    return h / 2;
  }
});
                    

titleBarFontFamily

Font family for the titleBar, if shown. CSS-compliant font family specifications are supported, including webfonts imported using the @font-face syntax. If not specified, the same font as specified for the groupFontFamily will be used.

This example renders the title bar in monospace font.

circles.set({
  dataObject: { groups: [
    { label: "ABC123", selected: true },
    { label: "DEF456" }]},
  titleBar: "inscribed",
  titleBarFontFamily: "monospace",
  groupFontFamily: "Arial, sans-serif"
});
                

titleBarMinFontSize

Minimum font size to draw the title bar's label. Two font sizing strategies are supported:

  • absolute: when the property is set to a numeric value, such as 27, the value will be interpreted as the number of pixels,
  • relative: when the property is set to a percentage, such as 40%, the font size will be calculated as a percentage of the title bar's maximum allowed height. This makes sense for the inscribed title bar type since the height of the inscribed box will depend on the size of the visualization.

Note that setting minimum font size to a large value may result in an empty title bar label or very unclear label ending in ellipsis

titleBarMaxFontSize

Maximum font size to draw the title bar's label. See titleBarMinFontSize attribute for details.

titleBarBackgroundColor

The background color of the title bar area.

titleBarTextColor

The text color for drawing labels in the title bar area.

titleBarTextPaddingLeftRight

Left and right (horizontal) padding to leave inside the title bar's area.

titleBarTextPaddingTopBottom

Top and bottom (vertical) padding to leave inside the title bar's area.

titleBarLabelDecorator

A callback decorator function to transform the label displayed by the titleBar component.

The callback function receives a hash with the following properties:

hoverGroup
The group the cursor currently covers over or undefined if none.
selectedGroups
An array of currently selected groups or an empty array.
label
Suggested label for the title bar. Changing this value will override the defaults.

The callback function may leave the default label as-is or it may change it. An undefined label will hide the title bar.

circles.set({
  dataObject: largeDataSet,
  titleBar: "topbottom",
  diameter: "80%",
  titleBarLabelDecorator: function(attrs) {
    if (attrs.hoverGroup) {
       attrs.label = "Hovering over: " + attrs.hoverGroup.label;
    } else if (attrs.selectedGroups.length > 0) {
       attrs.label = "Selected: " + attrs.selectedGroups.length + " group(s)";
    } else {
       attrs.label = "[hover or select something]";
    }
  }
});
                

Animations

expandTime

Duration of the open/close group animation (in seconds).

zoomTime

Duration of the zoom/unzoom group animation (in seconds).

rolloutAnimation

Sets the effect used to rollout a new model into view. The value should be any of the following strings: implode, rollout, tumbler, fadein, random.

The following example demonstrates each of these effects.

var model = {groups: [
  { label: "implode" },
  { label: "rollout" },
  { label: "tumbler" },
  { label: "fadein" },
  { label: "random" }]};

circles.set({
  modelChangeAnimations: "sequential",
  rolloutTime: 1,
  rolloutAnimation: "implode",
  dataObject: model,
  onBeforeZoom: function() { return false; },
  onBeforeSelection: function() { return false; },
  onGroupClick: function(info) {
    // Reset the model to replay the animation.
    circles.set("dataObject", null);
    circles.set("rolloutAnimation", info.group.label);
    circles.set("dataObject", model);
  }
});
                

rolloutTime

Duration (in seconds) of the rolloutAnimation sequence. To skip the animation sequence entirely, set the rolloutTime to zero.

pullbackAnimation

Sets the effect used to pullback the current model from view. The value should be any of the following strings: explode, rollin, tumbler, fadeout, random.

The following example demonstrates each of these effects.

var model = {groups: [
  { label: "explode" },
  { label: "rollin" },
  { label: "tumbler" },
  { label: "fadeout" },
  { label: "random" }]};

circles.set({
  modelChangeAnimations: "sequential",
  pullbackTime: 1,
  pullbackAnimation: "explode",
  dataObject: model,
  onBeforeZoom:      function() { return false; },
  onBeforeSelection: function() { return false; },
  onGroupClick:      function(info) {
    circles.set("pullbackAnimation", info.group.label);
    circles.set("dataObject", null);
    circles.set("dataObject", model);
  }
});
                

pullbackTime

Duration (in seconds) of the pullbackAnimation sequence. To skip the animation sequence entirely, set the pullbackTime to zero.

updateTime

Duration of the group weights' tween when update is called.

modelChangeAnimations

Controls how effects played on model changes are ordered and replayed with respect to one another. The following values are possible:

auto
the concurrency is chosen automatically depending on the target browser speed.
sequential
pullback animation runs fully before the rollout animation of the new model.
parallel
pullback animation and rollout animation run at the same time (concurrently). This may require more computational and graphic resources.

Note that sequential setting does not imply a rapid succession of several model changes will be reflected on screen (some of the intermediate changes may be skipped and never displayed). The following example shows a rapid succession of model changes in sequential mode.

circles.set({
  modelChangeAnimations: "sequential",
  rolloutAnimation: "implode",
  pullbackAnimation: "explode",
  pullbackTime: 1,
  rolloutTime: 1
});
window.setTimeout(function() {
  circles.set("dataObject", {groups: [{label: "g1"}]});
  circles.set("dataObject", {groups: [{label: "g2"}]});
  circles.set("dataObject", null);
  circles.set("dataObject", {groups: [{label: "g3"}]});
  circles.set("dataObject", {groups: [{label: "g4"}]});
  circles.set("dataObject", null);
  circles.set("dataObject", {groups: [{label: "g5"}]});
  circles.set("dataObject", {groups: [{label: "final"}]});
}, 500);
                

And a corresponding example in parallel mode.

circles.set({
  modelChangeAnimations: "parallel",
  rolloutAnimation: "implode",
  pullbackAnimation: "explode",
  pullbackTime: 1,
  rolloutTime: 1
});
window.setTimeout(function() {
  circles.set("dataObject", {groups: [{label: "g1"}]});
  circles.set("dataObject", {groups: [{label: "g2"}]});
  circles.set("dataObject", {groups: [{label: "g3"}]});
  circles.set("dataObject", {groups: [{label: "g4"}]});
  circles.set("dataObject", null);
  circles.set("dataObject", {groups: [{label: "g5"}]});
  circles.set("dataObject", {groups: [{label: "final"}]});
}, 500);
                

Selection

groupSelectionColor

Overlay color for selected groups.

groupSelectionOutlineColor

Color of the outline stroke for the selected groups.

groupSelectionOutlineWidth

Outline stroke width (in pixels) for the selected groups.

Hover

groupHoverColor

The color of an overlay applied to the group (or groups) the cursor hovers on.

groupHoverOutlineColor

The outline color of an overlay applied to the group (or groups) the cursor hovers on.

groupHoverOutlineWidth

The width of an outline stroke (in pixels) applied to the group (or groups) the cursor hovers on.

groupHoverHierarchy

If true, all parents of the hovered group are also included in the overlay. Compare the following two examples (hover on any group; the colors intentionally eye-watering).

circles.set({
  groupHoverOutlineColor: "#f00",
  groupHoverOutlineWidth: 3,
  groupHoverColor: "rgba(0,0,255,0.5)",
  dataObject: largeDataSet,
  groupHoverHierarchy: true
});
                    
circles.set({
  groupHoverOutlineColor: "#f00",
  groupHoverOutlineWidth: 3,
  groupHoverColor: "rgba(0,0,255,0.5)",
  dataObject: largeDataSet,
  groupHoverHierarchy: false
});
                    

Export

imageData

Returns the current state of the visualization as an image in the data URL format.

Unlike most attributes, this one accepts an optional object which can specify the image format details. This hash object can have the following keys:

format
format of the image data: image/png or image/jpeg
quality
if format is image/jpeg, specifies the desired quality of JPEG compression in the 0..1 range, where 1 means the highest quality and largest image data. Note that JPEG images are always opaque, even if the background color is specified as transparent. Use PNG images to handle background transparency.
pixelRatio
the pixel ratio to use when producing the export image. Use a value larger than 1, such as 2 to create a higher-resolution image.

The visualization image can be used by the application in many different ways. On certain browsers, it is possible to trigger a dialog allowing the user to save the image to the local disk. The image data can be also sent to a server that will do further processing (for example save it or send it via e-mail somewhere).

Heads up!

If the attribution logo was fetched from a different domain than the page that embeds the visualization, getting the visualization image will not be possible due to security constraints.

The following example logs the size of the visualization image (the data URL string, not actual bytes) in various formats to console.

circles.set({
  backgroundColor: "rgba(0,0,0,0)",
  dataObject: largeDataSet,
  onRolloutComplete: function() {
    var opts = [
      { format: "image/png" },
      { format: "image/jpeg", quality: 0.2 },
      { format: "image/jpeg", quality: 0.4 },
      { format: "image/jpeg", quality: 0.6 },
      { format: "image/jpeg", quality: 0.8 },
      { format: "image/jpeg", quality: 1 }
    ];
    $(opts).each(function(i, format) {
      console.log(format, "Image takes: " +
                  (circles.get("imageData", format).length / 1024).toFixed(0) + "kB");
    });
  }
});
                

This example opens up a popup window (remember to disable popup blocker) with three renderings of the same visualization at different pixel ratios.

circles.set({
  backgroundColor: "rgba(0,0,0,0)",
  dataObject: largeDataSet,
  rolloutTime: 0.5,
  rolloutAnimation: "implode",
  deferLabelRedraws: 0.25,
  labelRedrawFadeInTime: 0.5,
  onRolloutComplete: function() {
    var opts = [
      { format: "image/png", pixelRatio: 0.5 },
      { format: "image/png", pixelRatio: 1 },
      { format: "image/png", pixelRatio: 1.5 }
    ];

    var layout = circles.get("layout");
    var popup = window.open("", "",
            "innerWidth=" + (layout.w + 30) + "," +
            "innerHeight=" + (layout.h + 100) + "," +
            "resizable=yes,scrollbars=yes");

    if (popup) {
      popup.document.write('<!DOCTYPE html><html lang="en"><head><title>Image export example</title></head><body>');
      popup.document.write('<p style="font-family: sans-serif;">Exported bitmaps at various pixelRatios.</p>');
      $(opts).each(function(i, format) {
        console.log("Exporting at pixelRatio: " + format.pixelRatio);

        popup.document.write("<p>Pixel ratio: " + format.pixelRatio + "</p>");
        popup.document.write("<p><img src='" + circles.get("imageData", format) + "' /></p>");
      });
      popup.document.write('</body></html>');
    } else {
      console.log("Could not open demo popup window.");
    }
  }
});
                

Interactions

selection

getter

Calling get("selection") returns an object with the currently selected groups. The object contains one property, groups, containing an array of references to the model's groups. For example.

circles.set("dataObject", { groups: [
  { label: "Group 1", selected: true },
  { label: "Group 2" },
  { label: "Group 3", selected: true },
  { label: "Group 4" }]});

console.log(circles.get("selection").groups);
                    
setter

When setting the selection, various syntax flavors are accepted.

  • A string identifier of the group to select.

    circles.set("selection", "1");
  • An array of string identifiers of all groups to select.

    circles.set("selection", ["1", "2.1"]);
  • A hash with a key all indicating the selection applies to all groups, or a key groups with an array of group identifiers, and the desired selection state under the key selected.

    This selects all groups, for example:

    circles.set("selection", {all: true, selected: true});

    This selects all groups, and then deselects certain groups:

    circles.set("selection", {all: true, selected: true});
    circles.set("selection", {groups: ["2.1", "1.2"], selected: false});
                            

    When a group under a collapsed parent is selected the parent will open up automatically. This default behavior can be controlled using the open attribute. The following example opens up the parent group (root) explicitly on selecting Group 4.

    circles.set({
      visibleGroupCount: 3,
      dataObject: { groups: [
        { label: "Group 1" },
        { label: "Group 2" },
        { label: "Group 3" },
        { label: "Group 4", id: "group.4" }
      ]}
    });
    
    // Will bring group 4 into view by opening up the parent.
    circles.set("selection", { groups: ["group.4"], selected: true, open: true });
                            

Any selection changes affect only the groups specified in the selector. No callback events are triggered for selection changes introduced via API.

open

getter

Calling get("open") returns an object with those groups that have the open attribute set to true, typically groups with children groups and an expander widget in the open state. The returned object contains one property, groups, containing an array of references to the model's groups. This may include the root group, as in this example.

circles.set({
  visibleGroupCount: 2,
  dataObject: {
    open: true,
    label: "I'm the root!",
    groups: [
      { label: "Bad news"   },
      { label: "Good news"  },
      { label: "Other news" }
    ]
  }
});
console.log(circles.get("open").groups);
                    
setter

When altering the open state of groups, all syntax flavors from the selection attribute are supported. For example, given the following data model:

circles.set({
  visibleGroupCount: 2,
  dataObject: {
    id: "root",
    groups: [
      { label:"Group 1", id: "1", groups: [
        { label:"Group 1.1" },
        { label:"Group 1.2" },
        { label:"Group 1.3" }
      ]},
      { label:"Group 2", id: "2", groups: [
        { label:"Group 2.1" },
        { label:"Group 2.2" },
        { label:"Group 2.3" }
      ]},
      { label:"Group 3", id: "3", groups: [
        { label:"Group 3.1" },
        { label:"Group 3.2" },
        { label:"Group 3.3" }
      ]}
    ]
  }
});
                    

We can open and collapse groups using the following.

  • A string identifier of the group.

                              circles.set("open", "root");
                            
  • An array of string identifiers of all involved groups.

                              circles.set("open", ["1", "2"]);
                            
  • A hash with a key all indicating the operation applies to all groups, or a key groups with an array of group identifiers, and the desired state under the key open.

    This opens up all groups, for example:

                              circles.set("open", {all: true, open: true});
                            

    This opens up all groups, and collapses certain groups:

    circles.set("open", {all: true, open: true});
    circles.set("open", {groups: ["2"], open: false});
                            

Only the groups specified in the selector are affected. No callback events are triggered for changes introduced via API.

zoom

getter

Calling get("zoom") returns an object with those groups that have the zoom attribute set to true, typically groups which the user zoomed-in by double clicking or tap-and-holding. The returned object contains one property, groups, containing an array of references to the model's groups.

setter

When altering the zoom state of groups, all syntax flavors from the selection attribute are supported.

  • A string identifier of the group.

                              circles.set("zoom", "1");
                            
  • An array of string identifiers of all involved groups.

                              circles.set("zoom", ["1.1", "2.1"]);
                            
  • A hash with a key all indicating the operation applies to all groups, or a key groups with an array of group identifiers, and the desired state under the key zoomed.

    This zooms-in on all groups, for example (although it makes little sense in practice because when all groups are zoomed-in their corrected weights will be equal their original weights):

                              circles.set("zoom", {all: true, zoomed: true});
                            

    This clears the zoom state from certain groups:

    circles.set("zoom", {all: true, zoomed: true});
    circles.set("zoom", {groups: ["2", "1.1"], zoomed: false});
                            

Only the groups specified in the selector are affected. No callback events are triggered for changes introduced via API.

Attribution

The image to display for the attribution. The image can be specified as a relative or absolute HTTP URL or a data URI. When using HTTP URLs, the image should to be preloaded before visualization is embedded, otherwise it may not be immediately visible. For this reason, the data URIs are recommended. You can use services like dataurl.net to convert your images to data URIs.

The demo version and all branded versions of Circles visualization have the attribution image locked. Contact Carrot Search for licensing if you need a fully customizable version.
circles.set("attributionLogo", "../demos/assets/img/logo-stub.png");
                

attributionUrl

The URL to open when the user clicks on the attributionLogo. Can be undefined.

attributionPositionX

Horizontal position for the attribution logo, expressed in pixels or as a percentage of the available container's width. "0%" means the left side of the visualization container, "100%" means the right side of the visualization container.

attributionPositionY

Vertical position for the attribution logo expressed in pixels or as percentage of the available container's height. "0%" means the top side of the visualization container, "100%" means the bottom side of the visualization window.

attributionSize

A callback function used to adjust or compute the attribution image's width and height. The callback function receives an associative array with the following properties.

imageWidth
The current width of the image, in pixels.
imageHeight
The current height of the image, in pixels.
layout
An object reflecting current layout. The hash will contain the following properties: x, y, w, h. They reflect the visualization's client area (upper left corner's position, width and height.

The callback function is expected to modify (or leave unchanged) imageWidth and imageHeight properties. Any return value returned by the callback is ignored.

The callback function is called once per layout call for performance reasons.

The following demo places the attribution logo right in the center of the visualization and resizes it to 33% of the available size, keeping the image proportional.

The logo is locked to this branded release's default one but the example will work.
circles.set({
  attributionLogo: "../demos/assets/img/logo-stub.png",
  attributionPositionX: "50%",
  attributionPositionY: "50%",
  attributionStayOnTime: undefined,
  attributionSize: function(attrs) {
    with (attrs.layout) {
      var aspect = attrs.imageWidth / attrs.imageHeight;
      if (w > h) {
        attrs.imageHeight = h * 0.5;
        attrs.imageWidth  = attrs.imageHeight * aspect;
      } else {
        attrs.imageWidth  = w * 0.5;
        attrs.imageHeight = attrs.imageWidth * aspect;
      }
    }
  }
});
                

attributionStayOnTime

Duration (in seconds) after which the attribution logo is removed from view. An undefined value or zero indicates the attribution logo will stay on forever.

attributionFadeInTime

Fade-in transition duration (in seconds) for the attribution logo (if attributionLogo is defined).

attributionFadeOutTime

Fade-out transition duration (in seconds) after attributionStayOnTime expires and the attribution logo should be removed from view.

Debugging

logging

Enables logging of some debug information to console.

textureMappingMesh

If true the texture mapping grid will be drawn on top of labels. Useful for adjusting texture mapping parameters. See radialTextureStep for an example where this option is used.

times

Execution times and statistics. The details of the returned hash object are not guaranteed to work between versions but it may turn useful for debugging performance problems.

window.setTimeout(function() {
  console.log("Execution time stats: ", circles.get("times"));
}, 500);