ZoomCharts Documentation
PieChart / FacetChart Data
Pie Chart and Facet Chart share the same data structure, therefore following documentation applies to both of these chart types.
Data format
A typical data object looks like this:
{ "subvalues": [ { name: "First slice", value: 1 }, { name: "Second slice", value: 2 }, { name: "Third slice, expandable", value: 3, subvalues: [ { name: "First subslice of third slice", value: 1 }, { name: "Second subslice of third slice", value: 2 }, { name: "Third subslice of third slice", value: 3 }, ] }, ] }
There is a difference in available properties between the root object (which represents the whole root pie) and the nested objects, which represent individual slices (plus subslices if there any). All properties mentioned below are optional, unless specified otherwise.
The root object has the following properties:
subvalues
- mandatory. Contains an array of slice data objects.id
- an optional ID of the pie. If this is set, it must match the ID of the pie which was requested. If it is not set, it is assumed to be the same as the requested ID.name
- user friendly name of the whole pie. This is not visible in PieChart, but FacetChart may display it.extra
- extra data attached to the node. While technically any other properties will work for extra data, future ZoomCharts versions may introduce new properties which can then have the same name. Theextra
property is guaranteed NOT to be ever used by ZoomCharts itself.error
- If the data loading failed for some reason, this can contain an error string. This message will be visible in the browser console.
The following properties of the root object are used only in the case of partial loading, otherwise they are ignored:
offset
- This specifies the starting index of thesubvalues
array in the data source. If not set, it is assumed to be 0.limit
- specifies the size limit of thesubvalues
array and is used to determine if more data is available or not. Ifsubvalues
array contains less items thanlimit
, or iflimit
isn't set at all, then it is assumed that there is no more data available in the data source. This value must be at least as big as the requested limit.afterSum
- specifies the sum of all values after thesubvalues
array.beforeSum
- specifies the sum of all values after thesubvalues
array.sum
- specifies the sum of all values in the data source, including those before, after, and inside thesubvalues
array.
For PieChart (but not FacetChart), if partial loading is used, at least two of afterSum
, beforeSum
and
sum
are mandatory. It doesn't matter which two of them are set, and you can also set all three, but the minimum is two. The
omitted property will be calculated using the the ones that are set and the values in the subvalues
array. If you set all
three properties, the following equation must hold:
beforeSum + afterSum + sumOfValues(subvalues) = sum
If this requirement is not met, the behavior of the chart is undefined.
The slice objects have the following properties:
value
- mandatory. The value of the slice. For PieChart, this cannot be negative (but it can be zero).id
- optional ID of the slice (must be unique in the entire data set). If this is set, then it indicates that the slice can be expanded.name
- user friendly name of the slice, shown in the labels.nameLegend
- name shown in the legend. If not set, it will be the same asname
.subvalues
- an array of slice data objects. If this is set, then the slice will be expandable, no matter if it hasid
set or not. Also, the subvalues are considered to be complete - that is, even if partial loading is used, it will not request more data for this sub-pie.style
- slice style for this specific slice
Data sources
As all charts, Pie Chart and Facet Chart can load data in four different ways - preloaded data, data function, data URL and API calls. Data function and data URL are mutually exclusive. If both are specified, data function is used and data URL is ignored.
All loaded data is cached and is not requested again, unless reloadData()
or
replaceData()
are called to clear the cache.
Preloaded data
Preloaded data is specified directly in the chart settings via the
preloaded
setting:
new PieChart({ data:{ preloaded: { subvalues: [ ... ] } }, ... });
This is useful if the data is small and you wish to avoid the overhead of dynamic loading, or if you wish to seed the chart with initial data that does not need to be dynamically loaded. Preloaded data will be always loaded, even if data function or data URL are specified alongside of it.
Data function
The data function has five parameters, in this order:
id
- the ID of the pie (parent slice) that needs slice data. The root pie will have anid
of empty string, unlessnavigation.initialDrilldown
has specified otherwise.limit
- number of slices to return. Used only for partial loading.offset
- index of the first slice to return. Used only for partial loading.success(data)
- callback that must be called once the data has arrived. The single parameter to this callback is the root data object.fail(error)
- callback that must be called if the data could not be fetched. The single parameter is an optional error message that will be displayed in the browser console.
The callbacks can be called later (for example, in a response to an AJAX request), or they can be called immediately. The callbacks are only valid for a single call, and after one is called, the other may not be called.
Data URL
The data URL will have the same parameters appended to the URL as the data
function - id
, limit
and offset
. The data returned must be in JSON format.
API Calls
Both FacetChart and PieChart support several API calls that manipulate the data cache directly:
addData()
- adds new data to the cache, potentially overriding existing data in the cache.reloadData()
- clears the data cache, adds preloaded data, and triggers a reload of the chart.replaceData()
- clears the data cache and adds the given data in a single call. Preloaded data is not added to the chache.
Partial loading
Normally data is requested for an entire pie, and new requests are issued when the user drills down to a previously unvisited level. However if you have a pie with very many slices (thousands), it may be impractial to download all that data at once. Instead PieChart and FacetChart support loading just a subset of all the slices in a single pie. As the user navigates the chart, more slices are requested as needed. To use this feature, several conditions must be met:
- The data source (function or URL) must support the
offset
andlimit
parameters for returning partial data (see below for an explanation of these parameters) - For PieChart (but not FacetChart), the response must include least two of
afterSum
,beforeSum
,sum
. If all three are included, the following equation must hold true:beforeSum + afterSum + sumOfValues(subvalues) = sum
- Slice filters are not allowed (
filters.sliceFilter
must not be set) - Data sorting must be disabled (
data.sortField
must not be set) - Automatic categorization must be disabled (
data.autoCategories
must not be set) - Zero values must be allowed (
filters.allowZeroValues
must betrue
. Default value isfalse
!) - Partial loading must be enabled (
data.partialLoad
must betrue
, which is also the default)
If all this is met, then the chart will attempt to request the data partially. Each request will thus have meaningful values for
offset
and limit
parameters. The offset
parameter specifies starting at which slice to return the result (the offset
of first slice is 0
), and the limit
parameter specifies the minimum amount of slices to return. More slices than limit
may be returned, but not less. The only exception is when the data source runs out of slices.
In the root object of the returned data there can also be offset
and limit
properties which describe the returned data.
The offset
property, if set, must match the one that was passed in the request. If it is not set, then it is assumed to be 0.
It is preferred that offset
be the same as requested, however that is not mandatory. What matters is that the data returned
overlaps with the data requested.
The limit
property, if set, specifies the maximum number of elements in the subvalues
array. It must not be set to a value
smaller than the limit
parameter in the request. If it is not set, of if it is set to a value which is larger than the count
of elements in the subvalues
array, then it is assumed that no further data is available.
It is possible for requests to return overlapping data intervals. In such cases, the later data overwrites the earlier data.
Partially loaded data is also possible if you load data via API calls. Simply add the offset
and limit
fields to the root
data object which is passed to .addData()
. In the case of PieChart, also add the afterSum
/ beforeSum
/ sum
fields
as described above. One additional concern is that the data cache only stores a contiguous range of slices. So when adding new
data to the cache, care must be taken to ensure that it is adjacent to data which is already there. The very first data object
however can be anywhere.
Partial loading and unstable data sources
A simple and intuitive way to implement partial loading would be to simply directly query a database with the specified parameters. For example, in a PHP/MySQL environment, this might result in a query similar to this:
select name, value from slices where parent_slice_id=$id limit ($offset, $limit);
This is indeed a very straightforward way of doing it, however it also carries with it a hidden danger. If the underlying data changes frequently (which is often the case for live data in a production environment), then this can result in problematic responses for the chart. Consider one such possible scenario:
- User A opens a PieChart which requests the first 10 slices and displays them. The rest of the slices are in the "Others" slice,
which is sized according to the
afterSum
value. - User B then adds a 5 new rows to the database, which results in 5 new slices prepended to the start of the PieChart.
- User A now clicks on "Others" and the PieChart requests the next 10 slices. However, since there are now 5 new slices at the start, then the rest of the slices have shifted. The response for slices 10 to 20 now includes 5 slices that were already returned earlier. However the PieChart cannot tell this, and it considers them to be new slices. This results in an incorrect display.
Similarly, when deleting slices at the start, some slices might be omitted from the chart. And changing values for sum
between
requests will also result in incorrect slice size calculations.
There are several ways of avoiding these problems and others might be possible too, depending on your exact situation:
- Don't use partial loading. Instead load all data at the same time, or at least load each pie as a whole.
- Use a snapshot or a historical view of the data which doesn't change while navigating the PieChart / FacetChart.
- Add a detection for changed data and call
reloadData()
when that happens.
Partial loading is a powerful tool for efficiently handling big data, but implementing it correctly can be difficult. PieChart and FacetChart can withstand problematic data without catastrophic failure (crashing/infinite loops/etc), but the resulting visualization will almost certainly be incorrect.
Automatic categorization
This is an alternative data format for PieChart and FacetChart. This is incompatible with partial loading and in fact any kind of dynamic loading at all. That is, the data can be initially loaded via any of the standard methods (data function, data URL, preloaded data, API calls), but no subsequent requests for additional data will be made. All data must be loaded at the start. In addition, initial navigation cannot be specified either, since pies won't have a determined ID.
The upshot to this is that the data hierarchy will be built by PieChart/FacetChart itself. Instead of supplying hierarchial data with slices that have subslices that have subslices, you provide a flat list of data objects and a list of properties ("categories") by which to group them. For example:
new PieChart({ data:{ preloaded: { subvalues: [ { make: "Volvo", year: 2014, name: "S60", value: 10 }, { make: "Volvo", year: 2015, name: "S60", value: 20 }, { make: "Volvo", year: 2016, name: "S60", value: 30 }, { make: "Volvo", year: 2014, name: "V70", value: 40 }, { make: "Volvo", year: 2015, name: "V70", value: 50 }, { make: "Volvo", year: 2016, name: "V70", value: 60 }, { make: "Volvo", year: 2014, name: "XC90", value: 70 }, { make: "Volvo", year: 2015, name: "XC90", value: 80 }, { make: "Volvo", year: 2016, name: "XC90", value: 90 }, { make: "VW", year: 2014, name: "Golf", value: 100 }, { make: "VW", year: 2015, name: "Golf", value: 110 }, { make: "VW", year: 2016, name: "Golf", value: 120 }, { make: "VW", year: 2014, name: "Passat", value: 130 }, { make: "VW", year: 2015, name: "Passat", value: 140 }, { make: "VW", year: 2016, name: "Passat", value: 150 }, { make: "VW", year: 2014, name: "Touran", value: 160 }, { make: "VW", year: 2015, name: "Touran", value: 170 }, { make: "VW", year: 2016, name: "Touran", value: 180 } ] }, autoCategories: ["make", "year"] } });
This produces a chart that has the make on the root drilldown level, then year on the second drilldown level, and model on the last drilldown level: