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 an HTML page using a simple JavaScript API.

Release notes with a list of bug fixes and new features are in the 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 an HTML5 application that requires an up-to-date browser. Circles is checked and supported on the two latest versions of the following browsers:

  • Chrome, on desktop and Android
  • Firefox
  • Safari, on macOS and iOS
  • Edge

The visualization needs a 2D canvas context and Pointer Events. It draws with WebGL 2 where available and with the 2D canvas otherwise. Other modern browsers, such as Opera or Samsung Internet, will most likely work, though support for these is not a priority. Internet Explorer is not supported.

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 the Adjusting size section for more information.

Data format

Input data passed to Circles needs to be 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.

NPM dependency

Since 2.3.8 If your software uses a modern build system, such as Webpack, you can use the Circles NPM package. The installation is different for the branded demo and the licensed non-branded variants, see the following sections for details.

Demo version

To add the Circles demo version to your project, run:

npm install @carrotsearch/circles

or

yarn add @carrotsearch/circles

Once the Circles dependency is installed, you can import the Circles class as follows:

import { Circles } from "@carrotsearch/circles";

const circles = new Circles({ ... });

If you hold a Circles license, see the licensed version section for installation instructions.

Licensed version

The NPM version of licensed Circles is available from our servers through a special URL. To add the licensed Circles dependency to your project:

  1. Upload your Circles license file at https://secure.carrotsearch.com.

  2. Copy the URL of the "npm" link under the Circles version you'd like to install.

    Carrot Search software downloads site, link to the dependency in the npm format
  3. Paste the copied link to your package.json as follows:

    {
      "dependencies": {
        "@carrotsearch/circles": "https://localhost/updateapp/download/circles/2.3.8/npm?A1f8b080000000000000a6d50dd4ec32014bef7298ebbd244da025d5b0c7617fa063ec1291c5d93962ec0a67b7bb16b9799c8ddf70fe8a137e402c1897ce827f7b2e199d8b47700a0fb108ee4db57f47e8af04ee8cd1e4266329d2fd26cbb270cde3052808f6918a6af67b0968d233ba7030f16cf4f304e2eee9fe09c2a1e93cb8f18c14cee442ea6cd0c18bb59b42d6f58c19928045f96ec45a6ef43ef29b48ed26d75bec2591bb17791ac3a436c1578bdf4089dffa7cfc1e503e8ef3b757ee567d7c14ff668e2257343acf8977238a696de9b8182ce67b4baf35bfb15a5b6eb4eab77a1ff74188f9e80aba6e4a4ca2d27592ad3d54f2d880895e47565b0520def4c69655e144a169663d9a98a8ba211b251db94a92b8184b2138257920c9a72d7fe00debc3eddeb010000Z"
      }
    }

    The hexadecimal data included in the query string of the URL represents your Circles license file and allows our servers to determine which Circles versions your license file covers.

    Alternatively, you can download the *.tgz file from the copied link, store it together with your code and reference the downloaded *.tgz file in your package.json.

  4. Once the Circles dependency is installed, import the Circles class and plugin classes as follows:

    import { Circles } from "@carrotsearch/circles";
    
    const circles = new Circles({ ... });

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 the 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 the Circles visualization. Circles will occupy the full width and height of the element.
  3. Call the static isSupported() function of the visualization class with the options you are about to use, 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 needs 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 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.

var options = {
  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" }
      ]}
    ]
  }
};

if (CarrotSearchCircles.isSupported(options)) {
  var circles = new CarrotSearchCircles(options);
} else {
  console.log("Visualization not supported.");
}
            

The reference to the returned instance (the circles variable) can be used to load a 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, a 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 raise it to the power of 0.7 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 one second; the pullback animation itself takes two 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

By default the visualization follows the size of its container element: when the element changes size, for example because the window was resized or the element was shown after being hidden, the visualization is laid out and redrawn for the new size (the autoResize attribute). To size the visualization under program control instead, set autoResize to false and call resize() when the size has changed, for example from a resize event listener on the window object as shown below. Until then, the picture is scaled proportionally to the element.

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

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

// Following the container's size (the default).
circles.set({
  autoResize: true,
  onLayout: function(attrs) {
    console.log("Layout.", attrs);
  }
});
                
// Sized under program control only.
circles.set({
  autoResize: false,
  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 the 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;
    })()
  }
});
            

Child groups are recursively assigned 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 the 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 the 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").length > 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 the new size, and returns true if it did. With the autoResize attribute on (the default) this happens automatically; see the Adjusting size section.

layout()

layout()

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

See the 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);
    }
  }
});
              

on(event, listener)

on(event, listener)

Since 3.0.0 Adds a listener to an event. The name of the event is the name of the corresponding callback option without the on prefix and starting with a lower case letter, for example groupClick for onGroupClick. Listeners already registered for the event, including the ones set through options, are preserved.

var listener = function(info) {
  console.log("Hover.", info.group ? info.group.label : "(none)");
};
circles.on("groupHover", listener);
console.log("Listeners: " + circles.get("onGroupHover").length);
              

off(event, listener)

off(event, listener)

Since 3.0.0 Removes a listener from an event. The listener must be the same function that was passed to on or set through the callback option.

dispose()

dispose()

Removes the visualization from its HTML element and releases the resources it holds: canvases, the WebGL context, event listeners and running animations. Call this method when the visualization is no longer needed, in particular before removing its element from the page. Browsers limit the number of WebGL contexts a page can have, so disposing of unused instances matters when the webgl renderer is in use.

After dispose(), the instance's methods do nothing: get returns undefined and set has no effect.

Embedding a new visualization in an element that already contains one disposes of the previous instance automatically.

Changed in 3.0.0 In earlier versions this method did nothing.

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, which 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.

The callback is the place to update your own elements that follow the visualization, for example using groupShape(). It should not read pixels from the visualization's canvases: with the webgl renderer the picture is not complete at the time of the call. To copy each frame as it is drawn use the onFrameDrawn event; to get a single image use the imageData attribute.

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 (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 child groups exceeds the threshold set in the 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 zoomed 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 change 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 the 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, the onGroupSelectionChanging event should be used. See the 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 change 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.

onBeforeGroupDrawn

Since 2.3.9 A callback function invoked prior to each redraw operation on each group. The callback can be used to alter the color computed from the default color model for each group and on each repaint cycle. This should be a last-resort operation if none of the existing options match the requirements.

The callback receives a single parameters object with the following properties:

group
property with the data model of the group being redrawn.
groupColor
Fill color for the group. This color is computed from the current color model and, if changed, must be compatible with CanvasRenderingContext2D fillStyle requirements.

Only the group color can be altered. The group's label color cannot be changed: it is computed and pre-rendered once during the layout operation.

This is a performance-critical callback. If defined, it should return back to the visualization as quickly as possible.

The following example picks a random group color on each visualization redraw (results in a highly stroboscopic effect).

circles.set({
  dataObject: largeDataSet,
  onBeforeGroupDrawn: function (params) {
    params.groupColor = "#" + Math.floor(Math.random() * 0xFFFFFF).toString(16);
  },
});
              

A more realistic example is to somehow derive the color from the group model itself. For example, this callback repaints the group using a different color, depending on how many clicks it received (click on a group a few times).

circles.set({
  dataObject: largeDataSet,
  onBeforeGroupDrawn: function (params) {
    var colors = [
      "#a0a0a0", "#ff8080", "#80ff80", "#8080ff"
    ];
    params.groupColor = colors[(params.group.counter || 0) % colors.length];
  },
  onGroupClick: function(params) {
    params.group.counter = (params.group.counter || 0) + 1;
  },
  onBeforeZoom: function() { return false; }
});
              

Changed in 3.0.0 With the webgl renderer, groupColor must be a CSS color string (any syntax the browser understands). Other values the canvas API accepts as a fill style, such as gradients or patterns, are only supported by the canvas renderer; with webgl the group is drawn in its original color.

onFrameDrawn

Since 3.0.0 Called each time the visualization has finished drawing a frame: groups, hover and selection outlines and labels are all on the canvas. Unlike in onRedraw, the picture is complete at this point, with either renderer, so this is the place to copy it, for example to draw a reflection or a magnified view on a canvas of your own.

The callback receives a single parameters object with the following property:

canvas
the canvas element with the frame, or null with the svg renderer, which has no canvas. Its size in pixels is the size of the visualization multiplied by pixelRatio. Use it only during the callback, for example as the source of drawImage(): with the webgl renderer the content is gone once the browser has presented the frame, and the element is replaced when the renderer changes. The title bar and the attribution logo are drawn on a separate canvas and are not included.

Any code in this callback runs for every frame of every animation, so it should be fast. Copying only the part of the frame that is needed, as in the second example below, keeps it cheap.

The mobile demo uses this event to draw a reflection of the visualization's bottom edge.

var frames = 0;
circles.set({
  rolloutTime: 1,
  onFrameDrawn: function(info) {
    frames++;
  },
  onRolloutComplete: function() {
    console.log("Frames drawn: " + frames);
  }
});
var m = circles.get("dataObject");
circles.set("dataObject", null);
circles.set("dataObject", m);
              

The following example keeps a small, magnified copy of a part of the visualization in the corner of the page, updated with every frame.

var copy = document.createElement("canvas");
copy.width = 200;
copy.height = 200;
copy.style.cssText = "position: absolute; right: 10px; bottom: 10px; " +
  "border: 1px solid #888; background: #fff; z-index: 10";
document.getElementById("visualization").appendChild(copy);

circles.set({
  rolloutTime: 2,
  rolloutAnimation: "tumbler",
  onFrameDrawn: function(info) {
    var source = info.canvas;
    var ctx = copy.getContext("2d");
    ctx.clearRect(0, 0, 200, 200);

    // A 100x100 square where the two rings meet, to the right of the center,
    // drawn at twice its size. The frame's pixels are scaled by pixelRatio.
    var layout = this.get("layout");
    var ratio = layout.pixelRatio;
    var x = layout.centerx + layout.radii[0];
    var y = layout.centery;
    ctx.drawImage(source,
      (x - 50) * ratio, (y - 50) * ratio, 100 * ratio, 100 * ratio,
      0, 0, 200, 200);
  }
});
var m = circles.get("dataObject");
circles.set("dataObject", null);
circles.set("dataObject", m);
              

onRendererChanged

Since 3.0.0 Called when the renderer in use changes: after the renderer option was changed, or when the visualization had to switch to the canvas renderer because WebGL is not available or its context was lost. It is also called right after embedding if renderer was set to webgl and that request could not be met.

The callback receives a single parameters object with the following properties:

renderer
the renderer now in use: webgl or canvas.
reason
a human-readable explanation of the choice, for diagnostics.
circles.set("onRendererChanged", function(info) {
  console.log("Now rendering with: " + info.renderer + " (" + info.reason + ")");
});
circles.set("renderer", circles.get("activeRenderer") == "webgl" ? "canvas" : "webgl");
              

General

supported

A static property on CarrotSearchCircles equal to true if the browser has a 2D canvas, the baseline the visualization needs. Every supported browser has one, so the property is true wherever the script loads at all.

Deprecated in 3.0.0 This property is kept for backwards compatibility. It cannot tell whether the renderer a page asks for is available; use isSupported() instead, which takes the options the page is about to embed with.

isSupported()

Since 3.0.0 A static function on CarrotSearchCircles that returns true if a visualization with the given options can be displayed in this browser. Pass the options object you are about to give to the constructor; the argument may also be omitted, which checks the defaults.

The function returns false when the browser has no 2D canvas, or when renderer in the options is webgl and WebGL cannot be used. In the latter case the constructor would still embed the visualization, with the canvas renderer, as described under renderer; this function lets a page that insists on hardware accelerated rendering find that out beforehand. With auto, canvas or svg the WebGL check is not made.

Checking WebGL creates a throw-away context and compiles the shaders. This takes a few milliseconds and is repeated on each call, so call the function once, before embedding, rather than in a loop.

Heads up!

This function is static and must be called directly on the CarrotSearchCircles class, as shown in this example:

  if (CarrotSearchCircles.isSupported({ renderer: "webgl" })) {
    // Put embedding code here.
    console.log("WebGL rendering supported.");
  } else {
    // Display a warning, skip the visualization or embed with another renderer.
    console.log("WebGL rendering not supported.");
  }
                  

defaults

Since 3.0.0 A static, read-only property on CarrotSearchCircles: an object with the default values of all attributes.

  console.log("Default pixel ratio: " + CarrotSearchCircles.defaults.pixelRatio);
  console.log("Default renderer: " + CarrotSearchCircles.defaults.renderer);
                

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 the element's new 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). A null model object clears the visualization (removes any current model).

The model must be a tree: a group object may appear in it only once. A group under two parents or among its own descendants is drawn more than once or never finishes the layout. With assertions enabled, such a model is reported in the console, naming the group and where it appears, and ignored, so that the visualization keeps its 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. The 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 a 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 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 auto: the visualization draws at the browser's own pixel ratio (window.devicePixelRatio), so that it is as sharp as the rest of the page. The ratio is read again when the visualization is resized, including resize() called after the window moved to a screen of a different density or the page was zoomed. The pixel ratio in effect is reported as pixelRatio of the layout attribute.

With a number, the width and height of the canvas on which the visualization is drawn are the pixel dimensions of the HTML container element multiplied by that number, and the canvas is 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. A value of 1 draws one physical pixel per logical pixel regardless of the screen, which is faster on high-density screens at the cost of sharpness; a value such as Math.min(1.5, window.devicePixelRatio) caps the resolution on the densest screens.

The following examples draw the same picture at pixel ratios of 0.5, 1 and 2. Compare the sharpness of the labels and the speed of the animations; on a high-density screen the last example is what the default of auto gives.

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!

On mobile devices, make sure to correctly set the size of the page's viewport. Without a viewport declaration, the page is laid out at a width of roughly a thousand CSS pixels and scaled down to fit the screen, while the screen's pixel ratio stays at two or three. With the default pixelRatio of auto, the canvas is then two or three times that width for a picture displayed at a fraction of that size. Refer to the mobile demo's source code for an example, which also caps the pixel ratio at 2.

If drawing is slow on a high-density screen, in particular with the canvas renderer, set pixelRatio to 1 or cap it as shown above. The visualization is then drawn at a lower resolution, but its layout and all sizes in CSS pixels stay the same. With the webgl renderer, an oversized canvas is more likely to come out blurry than slow: the browser may allocate a smaller drawing buffer than requested and stretch it. The svg renderer draws the main picture as vector graphics and is not affected by pixelRatio at all.

Changed in 3.0.0 The pixel ratio only affects the resolution of the picture. All attributes that specify sizes in pixels (font sizes, paddings, line widths, the diameter and so on) are in logical (CSS) pixels of the page and the layout does not depend on the pixel ratio. In earlier versions some of these attributes, such as minRadialPadding and minAngularPadding, were in physical pixels and percentage font sizes grew with the pixel ratio; the picture at pixel ratios other than 1 may therefore differ slightly from the previous versions. Changing pixelRatio no longer reloads the data model. The default is now auto, the browser's pixel ratio; set pixelRatio to 1 to get the previous default.

renderer

Since 3.0.0 Selects how the visualization is drawn:

webgl
hardware accelerated rendering using WebGL 2. Groups, outlines and labels are drawn by shaders on the graphics card, which makes animations smooth also at high pixel ratios and for large data sets. If WebGL cannot be used, the visualization falls back to the canvas renderer and reports that through onRendererChanged. This renderer ignores the texture mapping attributes radialTextureStep, angularTextureStep and textureOverlapFudge, and textureMappingMesh shades labels instead of drawing a mesh; see the differences below.
canvas
software rendering using the 2D canvas API, as in the versions before 3.0.0.
svg
the visualization is an SVG document: groups are paths and labels are real text laid along arcs. The picture is resolution independent, so it stays sharp when zoomed or printed, and it can be inspected and copied like any other SVG. This is the slowest renderer for animations and large data sets; it is never chosen automatically.
Heads up!

Safari draws right-to-left scripts on curved labels in reversed order and without Arabic letter joining. This is a WebKit bug in the rendering of text on paths (bug 275313); the other renderers are not affected. Use the canvas or webgl renderer for such labels in Safari.

auto
uses webgl if it is available and hardware accelerated, and canvas otherwise. Because browsers limit the number of WebGL contexts on a page, in this mode only the first eight visualizations on a page use webgl.

The renderer can be changed at any time; the picture stays the same. If the WebGL context is lost and the browser does not restore it, the visualization continues with the canvas renderer. The two renderers produce the same picture up to minor differences in antialiasing. The differences in behavior are:

  • radialTextureStep, angularTextureStep and textureOverlapFudge only apply to the canvas renderer, which maps labels using a mesh of triangles. The webgl renderer maps labels exactly, for each pixel.
  • textureMappingMesh shades the area of each label with the webgl renderer; there is no mesh to show.
  • in onBeforeGroupDrawn, the webgl renderer only accepts CSS color strings.
  • the visualization's main canvas has a WebGL context with the webgl renderer, so a 2D context cannot be obtained from it. The canvas element is replaced when the renderer changes. To read the picture, use the onFrameDrawn event or the imageData attribute.
  • with the svg renderer there is an svg element in place of the main canvas. pixelRatio has no effect on it (it still applies to the title bar, the attribution and exported images), the texture mapping attributes do not apply, onBeforeGroupDrawn only accepts CSS color strings, and onFrameDrawn provides no canvas. imageData works as usual; the image is drawn by the canvas renderer. Labels are set as text, so they differ slightly in shape from the other renderers, which bend an image of the text.

To diagnose rendering problems, look at the browser's console. The loss and the restoration of the WebGL context and the switch to the canvas renderer are always reported there as warnings, with the time since embedding. With the logging attribute set to true at embedding time, the chosen renderer, the reason for the choice, the graphics hardware and the setup time are logged as well.

circles.set("renderer", "webgl");
console.log("Requested: " + circles.get("renderer") + ", in use: " + circles.get("activeRenderer"));
                

activeRenderer

Since 3.0.0 A read-only attribute that returns the renderer currently in use: webgl, canvas or svg. See the renderer attribute.

console.log("Rendering with: " + circles.get("activeRenderer"));
                

captureMouseEvents

The visualization by default captures mouse, touch and pen presses and prevents their default browser actions. 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

autoResize

Since 3.0.0 Whether the visualization follows the size of its container element. When true, the default, a change of the container's size lays out and redraws the visualization, as a call to resize() would: the window was resized, the page's layout changed, the element was shown after being hidden. Set it to false to size the visualization under program control, with resize().

The size is watched with the browser's ResizeObserver; in a browser without it, the visualization needs resize() calls as in earlier versions. See Adjusting size.

circles.set("autoResize", false);
                

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 the 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 of the onLayout event. 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 the ringShape attribute for defining custom ring shapes.

ringShape

While ringScaling 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 may, but need not, 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) the r_inner and r_outer properties. Any return value returned by the callback is ignored. Model groups are traversed in-depth, in pre-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 the 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 counter-clockwise 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 child 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 into 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}
  ]}
});
                

isGroupHoverable

Since 3.0.0 A callback used to determine whether a given group reacts to the pointer hovering over it. The callback function will receive a single argument: the group object from the dataObject model, and should return true for groups that can be hovered. When the callback is not set, all groups can be hovered.

A group excluded by this callback gets no hover outline and is not reported by onGroupHover, as if the pointer was over empty space. With groupHoverHierarchy enabled, excluded ancestors of the hovered group are left out of the outline. Clicks and double clicks are not affected. To turn hovering off for all groups, set the hover colors to transparent instead.

The following example lets only the groups without children be hovered.

circles.set({
  dataObject: {
    groups: [
      {label: "1", groups: [{label: "1.1"}, {label: "1.2"}]},
      {label: "2", groups: [{label: "2.1"}, {label: "2.2", groups: [{label: "2.2.1"}]}]},
      {label: "3"}
    ]
  },
  isGroupHoverable: function(group) { return !group.groups; }
});
                

Groups

groupOutlineWidth

Width of the outline stroke. A stroke width 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 desired effect, transparencies should be avoided.

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

rainbowStartColor

Start color to use if the 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

End color to use if the rainbow color model is used for coloring groups. See rainbowStartColor for a description of how the 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 to 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 the 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 gray
    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, respectively.

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 the minimum font size to a large value may result in empty labels (unlabeled groups) or very unclear labels ending in an 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

This attribute has been removed. Please take a look at groupLinePaddingRatio for a size-relative replacement.

groupLinePaddingRatio

Extra vertical padding added between lines in multi-line labels. The value of this attribute is a ratio multiplied by the label's line height. The ratio can be negative, for example groupLinePaddingRatio of -0.1 will shrink the space between lines by 10%. Note that doing so may cause glyphs from different lines to overlap.

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 them.

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 logical (CSS) pixels. See ratioAngularPadding for an explanation of 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 of how paddings work.

minRadialPadding

Minimum radial padding space, in logical (CSS) pixels. See ratioAngularPadding for an explanation of how paddings work.

radialTextureStep

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

Changed in 3.0.0 This attribute, like the other texture mapping attributes, applies to the canvas renderer only. The webgl renderer maps labels exactly and needs no tuning.

Texture mapping is an expensive operation that requires a trade-off: more subdivision polygons will result in higher quality but will take longer to render. 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, the 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 label quality. They select the canvas renderer explicitly, since with the webgl renderer these options have no effect.

circles.set({
  dataObject: largeDataSet,
  renderer: "canvas",
  noTexturingCurvature: 0,
  radialTextureStep: 15,
  angularTextureStep: 15,
  textureMappingMesh: true
});
                    
circles.set({
  dataObject: largeDataSet,
  renderer: "canvas",
  noTexturingCurvature: 0,
  radialTextureStep: 30,
  angularTextureStep: 30,
  textureMappingMesh: true
});
                    
circles.set({
  dataObject: largeDataSet,
  renderer: "canvas",
  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,
  renderer: "canvas",
  noTexturingCurvature: 0.2,
  radialTextureStep: 15,
  angularTextureStep: 15,
  textureMappingMesh: true
});
                    
circles.set({
  dataObject: largeDataSet,
  renderer: "canvas",
  noTexturingCurvature: 0.4,
  radialTextureStep: 15,
  angularTextureStep: 15,
  textureMappingMesh: true
});
                    
circles.set({
  dataObject: largeDataSet,
  renderer: "canvas",
  noTexturingCurvature: 0.8,
  radialTextureStep: 15,
  angularTextureStep: 15,
  textureMappingMesh: true
});
                    

angularTextureStep

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

noTexturingCurvature

Sets the threshold between normal label rendering and texture-mapping onto a slice of a circle. See the description and examples accompanying the 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. Applies to the canvas renderer only.

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 meantime 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 in 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 a group label's direction (angular or radial). The aspect ratio of a label is computed by dividing the width of the label box by its height (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 the 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 a 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 the minimum font size to a large value may result in an empty title bar label or a very unclear label ending in an ellipsis.

titleBarMaxFontSize

Maximum font size to draw the title bar's label. See the 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 hovers 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 roll out a new model into view. The value should be any of the following strings: implode, rollout, tumbler, fadein, implode3d, random.

The implode3d effect Since 3.0.0 flies groups into place in three dimensions, from behind the viewer. It requires the webgl renderer; the other renderers play implode instead. random never picks it.

The following example demonstrates each of these effects.

var model = {groups: [
  { label: "implode" },
  { label: "rollout" },
  { label: "tumbler" },
  { label: "fadein" },
  { label: "implode3d" },
  { 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 pull back the current model from view. The value should be any of the following strings: explode, rollin, tumbler, fadeout, explode3d, random.

The explode3d effect Since 3.0.0 breaks the visualization into tiles that fly towards the viewer in three dimensions. It requires the webgl renderer; the other renderers play explode instead. random never picks it.

The following example demonstrates each of these effects.

var model = {groups: [
  { label: "explode" },
  { label: "rollin" },
  { label: "tumbler" },
  { label: "fadeout" },
  { label: "explode3d" },
  { 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 the sequential setting does not imply that 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.

groupSelectionOutlineStyle

Since 2.3.9 Selects the type of overlay painted over the selected group. The following values are supported:

  • slice (default): a pie slice (wedge) outlining the group and going to the circle's center.
  • group: only the group (outline shape).
  • none: no outline is drawn.

The following example demonstrates the slice and group outline types. Note groupHoverHierarchy is turned off to limit the outline to just the selected groups.

circles.set({
  groupSelectionColor: "#f00",
  groupSelectionOutlineWidth: 3,
  groupSelectionOutlineColor: "rgba(0,0,255,0.5)",
  dataObject: largeDataSet,
  groupHoverHierarchy: false,
  groupSelectionOutlineStyle: "slice"
});
                    
circles.set({
  groupSelectionColor: "#f00",
  groupSelectionOutlineWidth: 3,
  groupSelectionOutlineColor: "rgba(0,0,255,0.5)",
  dataObject: largeDataSet,
  groupHoverHierarchy: false,
  groupSelectionOutlineStyle: "group"
});
                    

Hover

groupHoverColor

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

groupHoverOutlineColor

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

groupHoverOutlineWidth

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

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 are 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
});
                    

groupHoverOutlineStyle

Since 2.3.9 Selects the type of overlay painted over the hovered group. The following values are supported:

  • slice (default): a pie slice (wedge) outlining the group and going to the circle's center.
  • group: only the group (outline shape).
  • none: no outline is drawn.

The following example demonstrates the slice and group outline types. Note groupHoverHierarchy is turned off to limit the outline to just the hovered-on group.

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

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, by default the visualization's own. Use a value larger than 1, such as 2, to create a higher-resolution image; auto is the browser's pixel ratio.

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 also be 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.

Opening up the image using top-level data URIs (new window) is blocked on most browsers due to security considerations: see "top-level navigation blocked" section of this chart.

The following example logs (to the browser's console) the size of the data URL string for the same visualization image compressed using varying settings (the data URL string size is logged, not actual image bytes).

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");
    });
  }
});
                

In the following example, we open up a new browser window (or tab) and populate the img element with the data URI acquired from the current state of the visualization:

circles.set({
  dataObject: { groups: [
    { label: "Click me to get the image", trigger: true, weight: 5 },
    { label: "Group 1" },
    { label: "Group 2" },
    { label: "Group 3" },
    { label: "Group 3" }
  ]},
  onGroupClick: function (event) {
    if (event.group.trigger) {
      let htmlContent = "<html><body><img width='100%' src='$data' />world</body></html>";
      htmlContent = htmlContent.replace("$data", 
        circles.get("imageData", {
          format: "image/png"
        }));

      const newWindow = window.open();
      newWindow.document.write(htmlContent);
      newWindow.document.close();
    }
  }
});

This example opens up a popup window (remember to disable the 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 the API.

open

getter

Calling get("open") returns an object with those groups that have the open attribute set to true, typically groups with child 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 the 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 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 the 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 be preloaded before the 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 a percentage of the available container's height. "0%" means the top side of the visualization container, "100%" means the bottom side of the visualization container.

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 the 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) the 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 75% of the inner ring's size, keeping the image proportional.

The logo is locked to this branded release's default one but the example will work.
var innerRingRadius = 0;
circles.set({
  attributionLogo: "../demos/assets/img/logo-stub.png",
  attributionPositionX: "50%",
  attributionPositionY: "50%",
  attributionStayOnTime: undefined,
  onLayout: function(layoutData) {
    // Store the computed internal ring radius.
    innerRingRadius = layoutData.radii[0];
  },
  attributionSize: function(attrs) {
    with (attrs.layout) {
      var angle = Math.atan2(attrs.imageWidth, attrs.imageHeight)
      var scale = 0.75;
      attrs.imageWidth  = scale * 2 * innerRingRadius * Math.sin(angle);
      attrs.imageHeight = scale * 2 * innerRingRadius * Math.cos(angle);
    }
  }
});
                

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. With the webgl renderer, which uses no grid, the area of each label is shaded instead.

times

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

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