API reference
Carrot Search Circles is a modern, in-browser, interactive visualization of hierarchical data sets. It may resemble what is known as "pie-charts" in statically rendered software but the visualization can be interactive, allowing drill-downs or adjustments at runtime.
Technically, Circles is an HTML5 application, all modern desktop and mobile browsers supporting the canvas specification will be able to display the visualization. Circles can be integrated into a HTML page using a simple JavaScript API.
Release notes with a list of bug fixes and new features are in
changes.html
file at the top of the
distribution bundle. An up-to-date version for the latest stable release of Circles is always at:
get.carrotsearch.com/circles/api
.
Carrot Search HTML5 Circles is a HTML5 application that requires an up-to-date technology in the browser. Circles is checked and supported on the following browsers:
The visualization code will most likely work on other modern browsers (such as Opera), though support for these is not a priority. For older browsers the previous (Adobe Flash based) version of Circles can serve as a fallback.
The entire implementation and API of the visualization is contained in the
carrotsearch.circles.js
file. It handles all your code's
interactions with the visualization, including embedding, changing various properties,
loading new data and listening to events. The entry point to the API is an instance
of the CarrotSearchCircles
class created during embedding.
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 Adjusting Size section for more information.
Input data passed to Circles need to be as a JavaScript object defining the whole hierarchy of groups and their properties. Please see the dataObject
option for detailed documentation and examples.
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/cricles"; 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 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.
supported
property of the visualization class 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 need to point to the HTML
element you defined earlier. In most cases, you will be providing other options during
initialization as well, such as the dataObject
to display or some visual
customizations. See the options reference for a complete list of options.
The embedding element should have non-zero dimensions at the time of Circles initializes. In most
cases it is enough to initialize Circles once the DOM is ready. In certain cases, however,
especially when the page is accessed through the file:// protocol, the element will
receive its size a bit later. To avoid race conditions, you can initialize Circles in the
onload
event (or poll the element's dimensions periodically and call resize
).
<script> window.addEventListener("load", function() { // Perform Circles embedding here }); </script>
The following example shows typical embedding code with a minimal data model. This will also be our baseline for examples where the embedding isn't explicit.
if (CarrotSearchCircles.supported) { var circles = new CarrotSearchCircles({ id: "visualization", dataObject: { groups: [ { label:"Group 1", groups: [ { label:"Group 1.1" }, { label:"Group 1.2" } ]}, { label:"Group 2", groups: [ { label:"Group 2.1" }, { label:"Group 2.2" } ]} ] } }); } else { console.log("Visualization not supported."); }
The reference to the returned instance (the circles
variable) can be used to load new data
model or change settings of the visualization. The instance is ready to handle method calls as soon as the
constructor invocation returns. The following example sets the background color to fully opaque white. See
the options reference for a list of available methods and options.
circles.set({ backgroundColor: "rgba(255,255,255,1)" });
A data model can be loaded, optionally replacing the current model, by passing the dataObject
parameter either during embedding or by calling the set
method, as in the example below.
circles.set({ dataObject: { groups: [ { label:"Group 1", groups: [ { label:"Group 1.1" }, { label:"Group 1.2" }, { label:"Group 1.3" } ]}, { label:"Group 2", groups: [ { label:"Group 2.1" }, { label:"Group 2.2" } ]}, { label:"Group 3" } ] } });
Data models not in the format presented above need to be converted prior to visualization. In this example we will load JSON data dynamically and convert it to Circles format. We use jQuery to make Ajax requests, but any other library should work equally well. Note how the model changes after a short pause (fetching model data dynamically, network connection is required).
circles.set({dataObject: {groups: [ {}, {label: "loading..." }, {}, {}, {label: "Please wait" }, {} ]}}); $.ajax({ url: "../demos/assets/data/census-raw.json", dataType: "json", success: function(data) { var groups = [ ]; var states = data.groups; var pow = 0.7; for (var i = 0; i < states.length; i++) { var s = states[i]; groups.push({ label: s.Placename, weight: s.Pop, groups: [ // Ethnic data is very varied, we rise it to the power of 0.8 to // make the differences less pronounced. { label: "Hispanic", weight: Math.pow(s.PctHisp, pow) }, { label: "White", weight: Math.pow(s.PctNonHispWhite, pow) }, { label: "Black", weight: Math.pow(s.PctBlack, pow) }, { label: "Indian", weight: Math.pow(s.PctAmInd, pow) }, { label: "Asian", weight: Math.pow(s.PctAsian, pow) }, { label: "Hawaiian", weight: Math.pow(s.PctNatHawOth, pow) } ] }) } circles.set("dataObject", { open: true, groups: groups }); } });
To clear the visualization model load a null
dataObject
. The example below sets a pullback animation for the existing model and then clears it after 2 seconds. Note the sequence of two parameter calls: changes to pullbackAnimation
and pullbackTime
need to be applied to the previous model (not the null
model).
window.setTimeout(function() { circles.set({ pullbackAnimation: "explode", pullbackTime: 2 }); circles.set({ dataObject: null }); }, 1000);
If the visualization container element changes size the visualization will be scaled proportionally. To redraw it from scratch taking the new size into consideration use the code that periodically polls the container's dimensions and updates the visualization accordingly. Or attach a resize event listener to the window
object as shown below.
window.addEventListener("resize", function() { circles.resize(); });
window["_resizeHook"] = function() { circles.resize(); }; window.addEventListener("resize", window["_resizeHook"]);
window.removeEventListener("resize", window["_resizeHook"]); delete window["_resizeHook"]
Compare the behavior of visualization with and without the resize listener (resize the browser window once the example is visible).
// With the resize listener installed. circles.set("onLayout", function(attrs) { console.log("Layout.", attrs); });
// Without the resize listener. circles.set("onLayout", function(attrs) { console.log("Layout.", attrs); });
On mobile devices one should resize and redraw the visualization after the orientation of a mobile device changes:
window.addEventListener("orientationchange", function() { circles.resize(); });
Resizing and redrawing may be costly. You may consider throttling calls to resize
using a delayed timer function or deferring label redrawing using deferLabelRedraws
attribute.
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; })() } });
Children groups are recursively assigned with colors that introduce a gradient to the saturation and lightness components of their parent's color, as demonstrated in the example below, which shows HSL color values assigned to each group.
function modelGen(subgroups) { var arr = []; for (var i = subgroups.pop(); i > 0; i--) { arr.push({label: ""}); } if (subgroups.length > 0) { for (var i = arr.length; --i >= 0;) { arr[i].groups = modelGen(subgroups.slice()); } } return arr; } circles.set({ backgroundColor: "#fff", visibleGroupCount: 0, rainbowStartColor: "hsla( 0, 100%, 50%, 1)", rainbowEndColor: "hsla(360, 100%, 50%, 1)", groupColorDecorator: function(opts, params, vars) { params.group.label = "" + Math.round(vars.groupColor.h) + ", " + Math.round(vars.groupColor.s) + "%, " + Math.round(vars.groupColor.l) + "%"; }, dataObject: modelGen([3, 5, 10, 1])[0] });
If the default color model does not suit your needs you can customize group colors to your liking by using
the groupColorDecorator
callback.
By default the visualization code does not verify whether input options are valid, this is done to minimize the size of the
code required at runtime. During development, however, it is strongly recommended to turn on assertions by including
carrotsearch.circles.asserts.js
file. This automatically enables assertion checking for
attributes passed to the visualization via the set
method.
The example below shows invalid values of certain options and how they result in console warnings.
circles.set({ id: undefined, backgroundColor: "foo", diameter: "95px", onLayout: "fooBar", imageData: "newImage.png", showZeroWeightGroups: "true", rolloutAnimation: "invalid-value", visibleGroupCount: 1.5, angleStart: -10 });
If you are using Carrot2 or Lingo3G clustering engine, you can convert the JSON and XML data formats using the following jQuery snippet (for simplicity, a similar conversion could be done manually of course).
// Convert clusters to groups from Carrot2/Lingo3G JSON format. function convert(clusters) { return clusters.map(function(cluster) { return { id: cluster.id, label: cluster.phrases.join(", "), weight: cluster.attributes && cluster.attributes["other-topics"] ? 0 : cluster.size, groups: cluster.clusters ? convert(cluster.clusters) : [] } }); }; $.ajax({ url: "../demos/assets/data/carrot2/ben-and-jerry.json", dataType: "json", success: function(data) { new CarrotSearchCircles({ id: "visualization", dataObject: { groups: convert(data.clusters) } }); } });
And a similar conversion from the XML format.
// Convert clusters to groups from Carrot2/Lingo3G XML format. function convert(clusters) { return $(clusters).map(function(index, cluster) { cluster = $(cluster).detach(); // destroys the model, simplifies processing. var subgroups = cluster.children("group").size() > 0 ? convert(cluster.children("group")) : []; return { id: cluster.attr("id"), label: cluster.find("group > title > phrase").map(asText).toArray().join(", "), weight: cluster.attr("score") ? cluster.attr("score") : 0, groups: subgroups } }).toArray(); }; // Convert a node to text. function asText(i, e) { return $(e).text(); } $.ajax({ url: "../demos/assets/data/carrot2/ben-and-jerry.xml", dataType: "xml", success: function(data) { new CarrotSearchCircles({ id: "visualization", dataObject: { groups: convert($(data).find("searchresult > group")) } }); } });
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 to the new size. See Adjusting Size section for code snippets related to this function.
Performs full layout of the component, including redrawing of text labels. It is preferable not to use this method directly and rely calls to resize
instead.
See layout
attribute for read-only access to the most recent layout state if on-changed updates
are not needed.
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); } } });
Called after the visualization has parsed the new data model provided in the dataObject
option, but before the new data has been rendered. Callback functions receive one argument newDataObject
, it is the new data object the visualization is about to show.
In the context of each callback, this
points to the involved instance of CarrotSearchCircles
.
// Attach a pair of listeners. circles.set("onModelChanged", [ function(newDataObject) { alert("Model changed.", newDataObject); }, function(newDataObject) { console.log("Model changed.", newDataObject); }] ); // Set a new data model to verify it works. circles.set("dataObject", { groups: [{ label:"Group 1" }]}); // Dump the number of listeners to the console. console.log("Listeners: " + circles.get("onModelChanged").length);
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.
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, for example (such as the selected group's title).
In the context of the callback, this
points to the involved instance of CarrotSearchCircles
.
circles.set("onLayout", function(layoutData) { console.log("Layout data.", layoutData); }); circles.layout();
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 children groups exceeds the
threshold set in visibleGroupCount
option. For each involved group, the callback
function will be invoked with one parameter containing the following properties.
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 zooomed 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 changes the selection via an API call.
Note that one user action may result in many events (deselection of currently selected groups and selection of another group, for example). A global selection state is easier to track using onGroupSelectionChanged
event.
circles.set({ onGroupSelectionChanging: function(info) { console.log("onGroupSelectionChanging.", info); }, onGroupSelectionChanged: function(info) { console.log("onGroupSelectionChanged.", info); } }); console.log("Select and deselect groups.");
Called once after the selection has changed. The callback receives
one parameter: an object containing the groups
property with an array of references to the data objects representing the
groups that are currently selected.
For tracking selection changes on individual groups, onGroupSelectionChanging
event should be used. See code snippet there for an example.
In the context of the callback, this
points to the involved instance of CarrotSearchCircles
.
The callback is not invoked for API-initiated state changes, only for user interactions.
When multiple callbacks are present the entire set will be invoked, even if one or more callbacks changes the selection via an API call.
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:
group
metaKey
metaKey
. Note that this key modifier is known not to work
in Windows-based browsers.ctrlKey
ctrlKey
.shiftKey
shiftKey
.In the context of the callback, this
points to the involved instance of CarrotSearchCircles
.
The callback is not invoked for API-initiated state changes, only for user interactions.
circles.set("onGroupClick", function(info) { console.log("onGroupClick.", info); });
Called after a double click (or tap-and-hold gesture) was detected on a group. The callback will be invoked with an object containing the following properties:
group
metaKey
metaKey
. Note that this key modifier is known not to work
in Windows-based browsers.ctrlKey
ctrlKey
.shiftKey
shiftKey
.// Prevent the default behavior and toggle selection on double click. circles.set({ onBeforeZoom: function() { return false; }, onBeforeSelection: function() { return false; }, onGroupDoubleClick: function(info) { var state = info.group.selected ? true : false; this.set("selection", { groups: [info.group.id], selected: !state }); console.log((state ? "Deselected" : "Selected") + " group " + info.group.id); } });
In the context of the callback, this
points to the involved instance of CarrotSearchCircles
.
The callback is not invoked for API-initiated state changes, only for user interactions.
Since 2.3.9 A callback function invoked prior to each redraw operation on each group. The callback can be used to alter the color computed from the default color model for each group and on each repaint cycle. This should be a last-resort operation if none of the existing options match the requirements.
The callback receives a single parameters object with the following properties:
group
groupColor
CanvasRenderingContext2D
fillStyle
requirements.
Only the group color can be altered. The group's label color cannot be changed: it is computed and pre-rendered once during the layout operation.
This is a performance-critical callback. If defined, it should return back to the visualization as quickly as possible.
The following example picks a random group color on each visualization redraw (results in 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; } });
A static property on CarrotSearchCircles
equal to true
if visualization is supported on the current browser environment.
This property is static and must be accessed directly on CarrotSearchCircles
class, as shown in this example:
if (CarrotSearchCircles.supported) { // Put embedding code here. console.log("Visualization supported."); } else { // Display a warning or skip visualization. console.log("Visualization not supported."); }
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 new element's 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). An null
model
object clears the visualization (removes any 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 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 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 1. In such cases, the width and
height of the canvas on which the visualization is drawn will be equal to the
pixel dimensions of the HTML container element. For
pixelRatio
values greater/smaller than 1, the pixel dimensions of the canvas
will be smaller/larger than the dimensions of the enclosing HTML element and the canvas
will be stretched to fit in the container's client area.
For example, if you set pixelRatio
to 2 and if the size of the
enclosing HTML element is 400x400 pixels, the size of the canvas will
be 800x800 pixels. Such a configuration will better utilize the extra pixels
available on Retina displays, for example.
On most modern browsers you can retrieve the device-specific
pixel ratio from the window.devicePixelRatio
property.
The following examples demonstrate the difference between a pixel ratio higher and lower than the pixel resolution. Note the performance drop when pixel ratio is set to a large value.
circles.set({ dataObject: largeDataSet, titleBar: "inscribed", pixelRatio: 0.5 });
circles.set({ dataObject: largeDataSet, titleBar: "inscribed", pixelRatio: 1 });
circles.set({ dataObject: largeDataSet, titleBar: "inscribed", pixelRatio: 2 });
To boost the performance on iPads with Retina display set pixelRatio
to 2 and the width of the HTML container element to be at least one pixel less than the total screen width. Yes, we know it's weird but it speeds up rendering a lot.
When setting pixelRatio
to a value larger
than 1 on mobile devices, make sure to correctly set the size of the
page's viewport. The default viewport may be very large, leading to a
very large canvas. Refer to the mobile demo's source code for examples.
The visualization by default captures all mouse events and prevents them from
bubbling up the DOM hierarchy. This helps avoid interactions with system features
such as scrolling or zooming on touch-based devices. In scenarios where such
features should be available to the user this option should be set to
false
.
Let's capture all mouse events happening on the body object (a parent of the visualization example in the DOM hierarchy).
var hook = window["_loggingHook"] = function(evt) { console.log("Mouse event: " + evt.type); }; $("body").on("mousedown mouseup", hook);
$("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 });
The background color for the canvas behind the visualization. Visualizations with fully opaque or fully-transparent backgrounds may be slightly faster to draw. See Colors section for color specifications.
circles.set("backgroundColor", "#fff");
circles.set("backgroundColor", "rgba(0,0,255,.2)");
circles.set("backgroundColor", "hsla(120, 50%, 50%, 0.8)");
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 in onLayout
method. The returned value may be undefined
if no model is displayed or if the layout data is not yet available.
console.log("Current layout.", circles.get("layout"));
Diameter 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 ringShape
attribute for defining custom ring shapes.
While ringScaling
gives gives pretty good control over ring sizes, sometimes
a more fine-grained control is needed. The ringShape
attribute lets one
use a callback function to modify each group's inner and outer ring properties (and possibly
other ring shape properties in the future). The callback function
receives an associative array with the following properties.
The callback function is expected to modify (or leave unchanged)
r_inner
and r_outer
properties. Any return value returned
by the callback is ignored.
Model groups are traversed in-depth, in post-order (parent first).
The callback function is called once per layout call for performance reasons.
A combination of ringShape
and isGroupVisible
callbacks
can be used to create a "wedge" effect, as the following example shows.
circles.set({ visibleGroupCount: 0, isGroupVisible: function(group) { return !group.gap; }, ringShape: function(attrs) { if (attrs.group.wedge) { attrs.r_inner -= 40; attrs.r_outer += 40; } }, dataObject: { groups: [ {label: "1"}, {label: "2"}, {gap: true, weight: 0.1}, {label: "3", wedge: true}, {gap: true, weight: 0.1}, {label: "4"}, {label: "5"} ]} });
More complex customizations are of course possible. The following example stacks up groups on top of one another, creating a spiky shape that lies pretty far from visualization's defaults.
// Link up groups with their parents. We will need this // in the callback hook. Calculate max depth as we go. var model = dataSets["full-3-level.jsonp"].data; var depth = 0; var assignParent = function(parent, level) { depth = Math.max(level, depth); parent.groups && parent.groups.forEach(function(e) { e.parent = parent; assignParent(e, level + 1); }); }; assignParent(model, 0); var variance = 30; // Randomize each group's ring by this value. circles.set({ dataObject: model, rolloutTime: 2, rolloutAnimation: "implode", visibleGroupCount: 8, // Decrease the diameter by max. ring size variation diameter: function(box) { return Math.min(box.height, box.width) - 2 * variance * depth; }, // Calculate ring sizes varying them randomly and stacking each // on top of its parent (relies on depth-first preorder traversal). ringShape: function(attrs) { var group = attrs.group; if (group.parent) { var r_width = attrs.r_outer - attrs.r_inner; group.r_inner = attrs.r_inner = group.parent.r_outer; group.r_outer = attrs.r_outer = group.r_inner + r_width + Math.random() * variance; } else { group.r_inner = attrs.r_inner; group.r_outer = attrs.r_outer; } } });
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 counterclockwise 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 children groups will be displayed.
circles.set({ visibleGroupCount: 2, dataObject: { groups: [ {label: "G1"}, {label: "G2"}, {label: "G3"}, {label: "G4"} ] }, });
circles.set({ visibleGroupCount: 4, dataObject: { groups: [ {label: "G1"}, {label: "G2"}, {label: "G3"}, {label: "G4"} ] }, });
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 automatically to the current model.
circles.set({ isGroupVisible: function(group) { return !group.gap; }, dataObject: { groups: [ {label: "1", groups: [{label: "1"}, {label: "2"}, {label: "3"}]}, {gap: true, weight: 0.1}, {label: "2", groups: [{label: "1"}, {label: "2"}, {label: "3"}]}, {gap: true, weight: 0.1}, {label: "3", groups: [ {label: "1"}, {gap: true, weight: 0.1}, {label: "2"}, {gap: true, weight: 0.1}, {label: "3"}]}, {gap: true, weight: 0.1} ]} });
Width of the outline stroke. Stroke 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 desirable effect, transparencies should be avoided.
circles.set({ groupOutlineWidth: 10, groupOutlineColor: "rgba(0,0,0,0.2)" });
Start color to use if rainbow color model is used for coloring groups. A rainbow color model
will proportionally spread the color hue among top-level groups (starting at rainbowStartColor
and ending at rainbowEndColor
). Any sub-groups will be painted with the parent group's hue but with
varying degrees of saturation and lightness.
circles.set("dataObject", largeDataSet); circles.set("open", {all: true});
For custom coloring groupColorDecorator
should be used.
Start color to use if rainbow color model is used for coloring groups. See rainbowStartColor
for a description of how 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 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 sentiment attribute of each group.
var sentimentPainter = function(opts, params, vars) { // Sentiment is a custom property with values in the -1..+1 range, -1 means negative sentiment, // +1 -- positive, 0 -- neutral var sentiment = params.group.sentiment; if (!sentiment) { // Make neutral groups grey vars.groupColor.s = 0; vars.groupColor.l = 0; vars.groupColor.a = 0.2; } else { if (sentiment > 0) { // Make positive groups green vars.groupColor.h = 120; } else { // Make negative groups red vars.groupColor.h = 0; } // Make saturation and lightness depend on // the strength of the sentiment. vars.groupColor.s = 50 * (1 + Math.abs(sentiment)); vars.groupColor.l = Math.abs(60 * sentiment); // Indicate that we use the HSL model vars.groupColor.model = "hsl"; } }; circles.set({ groupColorDecorator: sentimentPainter, dataObject: { groups: [ { label: "Bad news", groups: [ { label: "Last penny lost", sentiment: -0.5 }, { label: "Bazinga doomed", sentiment: -1 } ]}, { label: "Good news", groups: [ { label: "iPads under $100", sentiment: 0.5 }, { label: "Martians are friendly", sentiment: 1 } ]}, { label: "Other news", groups: [ { label: "Vampires on the loose", sentiment: 0 }, { label: "Van Helsing to the rescue", sentiment: 0 } ]} ] } });
Picks the label color automatically depending on the group's color brightness. If the
brightness is below this threshold labelLightColor
is used, otherwise
labelDarkColor
is used. Setting the threshold to either 0 or 1 will pick
dark or light labels, correspondingly.
Automatic choice of label colors is also provided as part of groupColorDecorator
's functionality.
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 minimum font size to a large value may result in empty labels (unlabeled groups) or very unclear labels ending in ellipsis, as in this example:
circles.set({ dataObject: largeDataSet, groupMinFontSize: 30, groupMaxFontSize: 40 });
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 it.
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 pixels. See ratioAngularPadding
for an explanation 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 how paddings work.
Minimum radial padding space, in pixels. See ratioAngularPadding
for an explanation how paddings work.
Specifies the radial interval between "tiles" which are used to map rectangular label image onto the slice of the circle. Smaller numbers will result in higher quality.
Texture mapping is an expensive operation that requires a trade-off: more subdivision polygons will
result in high quality but will render longer. When high interactivity is expected (or on slower devices)
poorer textures may be a better choice instead of long delays required to render labels. In addition,
noTexturingCurvature
attribute allows one to define the threshold when the texture should not be
applied at all (when the curvature of the label is small enough that it can be drawn as a straight box).
textureMappingMesh
is helpful in debugging and estimating the needed value of this
parameter. The following examples show different texture mapping settings and how they affect labels
quality.
circles.set({ dataObject: largeDataSet, noTexturingCurvature: 0, radialTextureStep: 15, angularTextureStep: 15, textureMappingMesh: true });
circles.set({ dataObject: largeDataSet, noTexturingCurvature: 0, radialTextureStep: 30, angularTextureStep: 30, textureMappingMesh: true });
circles.set({ dataObject: largeDataSet, noTexturingCurvature: 0, radialTextureStep: 50, angularTextureStep: 50, textureMappingMesh: true });
The following example sets noTexturingCurvature
to different thresholds so that near-straight labels
are not textured at all (fully grayed boxes).
circles.set({ dataObject: largeDataSet, noTexturingCurvature: 0.2, radialTextureStep: 15, angularTextureStep: 15, textureMappingMesh: true });
circles.set({ dataObject: largeDataSet, noTexturingCurvature: 0.4, radialTextureStep: 15, angularTextureStep: 15, textureMappingMesh: true });
circles.set({ dataObject: largeDataSet, noTexturingCurvature: 0.8, radialTextureStep: 15, angularTextureStep: 15, textureMappingMesh: true });
Specifies the angular interval (in pixels) between "tiles" which are used
to map rectangular label image onto the slice of the circle. See the description
and examples accompanying radialTextureStep
attribute for more.
Sets the threshold between normal label rendering and texture-mapping onto a slice of a circle. See the description and examples accompanying radialTextureStep
attribute for more.
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.
Defers redrawing of labels for a given number of seconds after a layout. In case any changes are made to the visualization in the mean time that would result in another layout operation, redrawing of labels will be further delayed. This is a trick to speed up interactions on slower devices.
Compare how labels are drawn on the following examples (resize the window once the example is visible).
circles.set({ dataObject: largeDataSet, deferLabelRedraws: 0, labelRedrawFadeInTime: 0 });
circles.set({ dataObject: largeDataSet, deferLabelRedraws: 1, labelRedrawFadeInTime: 0 });
circles.set({ dataObject: largeDataSet, deferLabelRedraws: 1, labelRedrawFadeInTime: 1 });
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 group label's direction (angular or radial). The aspect ratio of a label is
computed by dividing the width and height of the label box (see ratioAngularPadding
for an explanation).
If the computed value is smaller than ratioAspectSwap
the label is rotated and printed
along the radial direction.
This parameter can be used to enforce directionality of labels by passing extreme ratios, as in the following example.
// Only radial labels. circles.set({ dataObject: largeDataSet, ratioAspectSwap: 10000 });
// Only angular labels. circles.set({ dataObject: largeDataSet, ratioAspectSwap: 0.0001 });
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 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 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 minimum font size to a large value may result in an empty title bar label or very unclear label ending in ellipsis
Maximum font size to draw the title bar's label. See 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 rollout a new model into view. The value should be any of the following strings:
implode
,
rollout
,
tumbler
,
fadein
,
random
.
The following example demonstrates each of these effects.
var model = {groups: [ { label: "implode" }, { label: "rollout" }, { label: "tumbler" }, { label: "fadein" }, { label: "random" }]}; circles.set({ modelChangeAnimations: "sequential", rolloutTime: 1, rolloutAnimation: "implode", dataObject: model, onBeforeZoom: function() { return false; }, onBeforeSelection: function() { return false; }, onGroupClick: function(info) { // Reset the model to replay the animation. circles.set("dataObject", null); circles.set("rolloutAnimation", info.group.label); circles.set("dataObject", model); } });
Duration (in seconds) of the rolloutAnimation
sequence. To skip the animation sequence
entirely, set the rolloutTime
to zero.
Sets the effect used to pullback the current model from view.
The value should be any of the following strings:
explode
,
rollin
,
tumbler
,
fadeout
,
random
.
The following example demonstrates each of these effects.
var model = {groups: [ { label: "explode" }, { label: "rollin" }, { label: "tumbler" }, { label: "fadeout" }, { label: "random" }]}; circles.set({ modelChangeAnimations: "sequential", pullbackTime: 1, pullbackAnimation: "explode", dataObject: model, onBeforeZoom: function() { return false; }, onBeforeSelection: function() { return false; }, onGroupClick: function(info) { circles.set("pullbackAnimation", info.group.label); circles.set("dataObject", null); circles.set("dataObject", model); } });
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 sequential
setting does not imply a rapid succession of several model changes
will be reflected on screen (some of the intermediate changes may be skipped and never displayed). The following
example shows a rapid succession of model changes in sequential mode.
circles.set({ modelChangeAnimations: "sequential", rolloutAnimation: "implode", pullbackAnimation: "explode", pullbackTime: 1, rolloutTime: 1 }); window.setTimeout(function() { circles.set("dataObject", {groups: [{label: "g1"}]}); circles.set("dataObject", {groups: [{label: "g2"}]}); circles.set("dataObject", null); circles.set("dataObject", {groups: [{label: "g3"}]}); circles.set("dataObject", {groups: [{label: "g4"}]}); circles.set("dataObject", null); circles.set("dataObject", {groups: [{label: "g5"}]}); circles.set("dataObject", {groups: [{label: "final"}]}); }, 500);
And a corresponding example in parallel mode.
circles.set({ modelChangeAnimations: "parallel", rolloutAnimation: "implode", pullbackAnimation: "explode", pullbackTime: 1, rolloutTime: 1 }); window.setTimeout(function() { circles.set("dataObject", {groups: [{label: "g1"}]}); circles.set("dataObject", {groups: [{label: "g2"}]}); circles.set("dataObject", {groups: [{label: "g3"}]}); circles.set("dataObject", {groups: [{label: "g4"}]}); circles.set("dataObject", null); circles.set("dataObject", {groups: [{label: "g5"}]}); circles.set("dataObject", {groups: [{label: "final"}]}); }, 500);
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 on.
The outline color of an overlay applied to the group (or groups) the cursor hovers on.
The width of an outline stroke (in pixels) applied to the group (or groups) the cursor hovers on.
If true
, all parents of the hovered group are also included in the overlay. Compare the
following two examples (hover on any group; the colors intentionally eye-watering).
circles.set({ groupHoverOutlineColor: "#f00", groupHoverOutlineWidth: 3, groupHoverColor: "rgba(0,0,255,0.5)", dataObject: largeDataSet, groupHoverHierarchy: true });
circles.set({ groupHoverOutlineColor: "#f00", groupHoverOutlineWidth: 3, groupHoverColor: "rgba(0,0,255,0.5)", dataObject: largeDataSet, groupHoverHierarchy: false });
Since 2.3.9 Selects the type of overlay painted for 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/jpeg
format
is image/jpeg
, specifies
the desired quality of JPEG compression in the 0..1 range, where 1
means the highest quality and largest image data. Note that JPEG images are always opaque, even
if the background color is specified as transparent. Use PNG images to handle background
transparency.1
,
such as 2
to create a higher-resolution image.
The visualization image can be used by the application in many different ways. On certain browsers, it is possible to trigger a dialog allowing the user to save the image to the local disk. The image data can be also sent to a server that will do further processing (for example save it or send it via e-mail somewhere).
If the attribution logo was fetched from a different domain than the page that embeds the visualization, getting the visualization image will not be possible due to security constraints.
The following example logs the size of the visualization image (the data URL string, not actual bytes)
in various formats to console
.
circles.set({ backgroundColor: "rgba(0,0,0,0)", dataObject: largeDataSet, onRolloutComplete: function() { var opts = [ { format: "image/png" }, { format: "image/jpeg", quality: 0.2 }, { format: "image/jpeg", quality: 0.4 }, { format: "image/jpeg", quality: 0.6 }, { format: "image/jpeg", quality: 0.8 }, { format: "image/jpeg", quality: 1 } ]; $(opts).each(function(i, format) { console.log(format, "Image takes: " + (circles.get("imageData", format).length / 1024).toFixed(0) + "kB"); }); } });
This example opens up a popup window (remember to disable popup blocker) with three renderings of the same visualization at different pixel ratios.
circles.set({ backgroundColor: "rgba(0,0,0,0)", dataObject: largeDataSet, rolloutTime: 0.5, rolloutAnimation: "implode", deferLabelRedraws: 0.25, labelRedrawFadeInTime: 0.5, onRolloutComplete: function() { var opts = [ { format: "image/png", pixelRatio: 0.5 }, { format: "image/png", pixelRatio: 1 }, { format: "image/png", pixelRatio: 1.5 } ]; var layout = circles.get("layout"); var popup = window.open("", "", "innerWidth=" + (layout.w + 30) + "," + "innerHeight=" + (layout.h + 100) + "," + "resizable=yes,scrollbars=yes"); if (popup) { popup.document.write('<!DOCTYPE html><html lang="en"><head><title>Image export example</title></head><body>'); popup.document.write('<p style="font-family: sans-serif;">Exported bitmaps at various pixelRatios.</p>'); $(opts).each(function(i, format) { console.log("Exporting at pixelRatio: " + format.pixelRatio); popup.document.write("<p>Pixel ratio: " + format.pixelRatio + "</p>"); popup.document.write("<p><img src='" + circles.get("imageData", format) + "' /></p>"); }); popup.document.write('</body></html>'); } else { console.log("Could not open demo popup window."); } } });
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 API.
Calling get("open")
returns an object with those groups that have the open
attribute set to true
, typically groups with children groups and an expander widget
in the open state. The returned object
contains one property, groups
, containing an array of references to the model's groups.
This may include the root group, as in this example.
circles.set({ visibleGroupCount: 2, dataObject: { open: true, label: "I'm the root!", groups: [ { label: "Bad news" }, { label: "Good news" }, { label: "Other news" } ] } }); console.log(circles.get("open").groups);
When altering the open state of groups, all syntax flavors from the selection
attribute
are supported. For example, given the following data model:
circles.set({ visibleGroupCount: 2, dataObject: { id: "root", groups: [ { label:"Group 1", id: "1", groups: [ { label:"Group 1.1" }, { label:"Group 1.2" }, { label:"Group 1.3" } ]}, { label:"Group 2", id: "2", groups: [ { label:"Group 2.1" }, { label:"Group 2.2" }, { label:"Group 2.3" } ]}, { label:"Group 3", id: "3", groups: [ { label:"Group 3.1" }, { label:"Group 3.2" }, { label:"Group 3.3" } ]} ] } });
We can open and collapse groups using the following.
A string identifier of the group.
circles.set("open", "root");
An array of string identifiers of all involved groups.
circles.set("open", ["1", "2"]);
A hash with a key all
indicating the operation applies to all groups, or
a key groups
with an array of group identifiers, and
the desired state under the key open
.
This opens up all groups, for example:
circles.set("open", {all: true, open: true});
This opens up all groups, and collapses certain groups:
circles.set("open", {all: true, open: true}); circles.set("open", {groups: ["2"], open: false});
Only the groups specified in the selector are affected. No callback events are triggered for changes introduced via API.
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 be equal their original weights):
circles.set("zoom", {all: true, zoomed: true});
This clears the zoom state from certain groups:
circles.set("zoom", {all: true, zoomed: true}); circles.set("zoom", {groups: ["2", "1.1"], zoomed: false});
Only the groups specified in the selector are affected. No callback events are triggered for changes introduced via API.
The image to display for the attribution. The image can be specified as a relative or absolute HTTP URL or a data URI. When using HTTP URLs, the image should to be preloaded before visualization is embedded, otherwise it may not be immediately visible. For this reason, the data URIs are recommended. You can use services like dataurl.net to convert your images to data URIs.
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 percentage of the available container's height. "0%" means the top side of the visualization container, "100%" means the bottom side of the visualization window.
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)
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.
Execution times and statistics. The details of the returned hash object are not guaranteed to work between versions but it may turn useful for debugging performance problems.
window.setTimeout(function() { console.log("Execution time stats: ", circles.get("times")); }, 500);