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:
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.
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.
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.
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.
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.
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).
Circles is a regular printable element of the page. You may want to customize the printing style sheets to hide non-relevant elements.
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.
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.
The NPM version of licensed Circles is available from our servers through a special URL. To add the licensed Circles dependency to your project:
Upload your Circles license file at https://secure.carrotsearch.com.
Copy the URL of the "npm" link under the Circles version you'd like to install.
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.
Once the Circles dependency is installed, import the Circles class
and plugin classes as follows:
import { Circles } from "@carrotsearch/circles";
const circles = new Circles({ ... });
Before Circles can display anything, it needs to be embedded in your HTML page. To embed Circles, your page needs to:
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.
isSupported() function of the visualization
class with the options you are about to use, to make sure the browser supports Circles.
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.
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)"
});
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);
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();
});
window["_resizeHook"] = function() { circles.resize(); };
window.addEventListener("resize", window["_resizeHook"]);
window.removeEventListener("resize", window["_resizeHook"]);
delete window["_resizeHook"]
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.
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)");
}));
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.
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
});
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"))
}
});
}
});
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());
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"));
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)");
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
});
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" }
]
}
});
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).
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.
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.
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 }
]
}
});
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:
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);
}
}
});
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);
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.
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.
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);
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.
Called after the rollout animation has completed.
In the context of the callback, this points to the involved instance of CarrotSearchCircles.
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);
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();
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)");
});
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.
true if the group has just been opened for
browsing of child groups, false if the group has just been closedIn 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.");
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:
true if the group has just been zoomed, false otherwiseIn 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.");
Called during group selection changes. For each selected or deselected group, the callback will be invoked with one parameter containing the following properties:
true if the group has just been selected, false otherwiseIn 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.");
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.
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.
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.
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:
groupmetaKeymetaKey. Note that this key modifier is known not to work
in Windows-based browsers.ctrlKeyctrlKey.shiftKeyshiftKey.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);
});
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:
groupmetaKeymetaKey. Note that this key modifier is known not to work
in Windows-based browsers.ctrlKeyctrlKey.shiftKeyshiftKey.
// 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.
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:
groupgroupColorCanvasRenderingContext2D
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.
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:
canvasnull 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);
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:
rendererwebgl or canvas.reason
circles.set("onRendererChanged", function(info) {
console.log("Now rendering with: " + info.renderer + " (" + info.reason + ")");
});
circles.set("renderer", circles.get("activeRenderer") == "webgl" ? "canvas" : "webgl");
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.
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.
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.");
}
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);
Identifier of the DOM element into which the visualization is to be embedded.
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.
The DOM element into which the visualization is to be embedded. Please see the id
option for additional considerations.
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:
selection, zoom state
or open state.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.(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.
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.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).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.
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
});
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.
Since 3.0.0 Selects how the visualization is drawn:
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.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.
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.onBeforeGroupDrawn, the webgl renderer only accepts CSS color
strings.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.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"));
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"));
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);
$("body").off("mousedown mouseup", window["_loggingHook"]);
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
});
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);
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)");
Horizontal position of the big circle's center. The position can be expressed in any of the following ways:
{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;
}});
Vertical position of the big circle's center. The position can be expressed in any of the following ways:
{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;
}});
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 of the big circle (outer ring). The diameter can be expressed in any of the following ways:
{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);
}});
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.
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.
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;
}
}
});
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
});
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.
window.clearTimeout(window['_animHookId']);
delete window['_animHookId'];
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();
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
});
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.
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 },
]
},
});
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"}
]
},
});
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}
]
},
});
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}
]}
});
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; }
});
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);
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)"
});
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.
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.
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.
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.
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 }
]}
]
}
});
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.
The label color to use on bright groups (see labelColorThreshold).
The label color to use on dark groups (see labelColorThreshold).
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");
Color of the zoomed-in group's decoration stroke. See zoomDecorationFillColor.
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.
Minimum font size used for drawing group labels. Two font sizing strategies are supported:
27,
the value will be interpreted as the number of pixels,
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
});
Maximum font size used for drawing group labels. See the
groupMinFontSize attribute for details.
This attribute has been removed. Please take a look at groupLinePaddingRatio for a size-relative
replacement.
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.
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("") + "»";
}
});
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).
Minimum angular padding for group labels. In logical (CSS) pixels. See ratioAngularPadding
for an explanation of how paddings work.
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.
Minimum radial padding space, in logical (CSS) pixels. See ratioAngularPadding
for an explanation of how paddings work.
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
});
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.
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.
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.
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
});
Duration of fade-in effect for labels after they are redrawn from scratch. The value is expressed in
seconds. Used in combination with deferLabelRedraws.
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
});
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.
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).
Expander widget's outline stroke width in pixels.
Expander widget's outline color.
Expander widget's fill color.
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;
}
});
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"
});
Minimum font size to draw the title bar's label. Two font sizing strategies are supported:
27,
the value will be interpreted as the number of pixels,
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.
Maximum font size to draw the title bar's label. See the titleBarMinFontSize
attribute for details.
The background color of the title bar area.
The text color for drawing labels in the title bar area.
Left and right (horizontal) padding to leave inside the title bar's area.
Top and bottom (vertical) padding to leave inside the title bar's area.
A callback decorator function to transform the label displayed by the titleBar
component.
The callback function receives a hash with the following properties:
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]";
}
}
});
Duration of the open/close group animation (in seconds).
Duration of the zoom/unzoom group animation (in seconds).
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);
}
});
Duration (in seconds) of the rolloutAnimation sequence. To skip the animation sequence
entirely, set the rolloutTime to zero.
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);
}
});
Duration (in seconds) of the pullbackAnimation sequence. To skip the animation sequence
entirely, set the pullbackTime to zero.
Duration of the group weights' tween when update is called.
Controls how effects played on model changes are ordered and replayed with respect to one another. The following values are possible:
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);
Overlay color for selected groups.
Color of the outline stroke for the selected groups.
Outline stroke width (in pixels) for the selected groups.
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"
});
The color of an overlay applied to the group (or groups) the cursor hovers over.
The outline color of an overlay applied to the group (or groups) the cursor hovers over.
The width of an outline stroke (in pixels) applied to the group (or groups) the cursor hovers over.
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
});
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"
});
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:
image/png or
image/jpegformat 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.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).
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.");
}
}
});
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);
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.
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);
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.
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.
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.
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.
circles.set("attributionLogo", "../demos/assets/img/logo-stub.png");
The URL to open when the user clicks on the attributionLogo. Can be undefined.
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.
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.
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.
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.
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);
}
}
});
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.
Fade-in transition duration (in seconds) for the attribution logo
(if attributionLogo is defined).
Fade-out transition duration (in seconds) after attributionStayOnTime expires
and the attribution logo should be removed from view.
Enables logging of some debug information to console.
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.
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);