premium
Using D3 in Ext JS
Introduction
Presenting data in a clear and compelling way is an important job for any application. D3 is an extremely popular choice for data visualization. The new d3 package makes it simpler than ever to integrate D3 into your Ext JS application.
In this guide, we will explore the various D3 components that are available in Ext JS.
Using the D3 Package
Your D3 package should be located within the packages folder that came with your Ext JS Premium purchase. This packages folder should be copied to the root packages directory of the ext folder you use to generate applications.
For instance, if you have generated your application using Sencha Cmd, you would move the d3 package from:
{premiumLocation}/ext/packages/d3
to:
{yourApplication}/ext/packages/d3
or:
{yourWorkspace}/ext/packages/d3
Once your package is in appropriately located, you’ll need to modify your app.json
file. Open {appDir}/app.json
and add “d3” to the “requires” block. It should now look like similar to the following code snippet:
"requires": [
"d3"
],
Do keep in mind that your requires block may contain other items depending on your default theme.
Your application should now be ready to use D3!
Note: The D3 package is only available in Ext Premium. For more information about Ext Premium and our other products, please check out the products page.
D3 Without a Cmd Application
You can also utilize the d3 package without a Cmd built application. However, you may need to use Cmd to build the initial CSS/JS builds if they do not appear in your package’s build folder.
We generally try to include pre-built packages, but sometimes they slip through the cracks and are not available until the next release.
Building a package is quick and simple. The process produces a singular JS/CSS file for inclusion in your non-Cmd application. To build these files, issue the following command from your CLI:
//target a specific theme
sencha package build {themeName}
Classic themes will just be theme names (triton, neptune, etc).
Modern themes will be prepended with “modern-“ (modern-neptune).
or
//build for all themes
sencha package build
You should now have a build folder that contains the following built files:
d3/{toolkit}/d3.js
d3/{toolkit}/{theme}/resources/d3-all.css
You can now include these outputs by whatever method you are using.
Components
Integrating D3 begins with the choice of rendering technology: SVG or Canvas. This is because D3 is not a drawing abstraction library (like Ext.draw.*
) and so the nature of the drawing surface is an important starting point. These base classes provide a structure to assist with rendering to the correct container element and updating when the container resizes.
These base classes streamline the process of copying simple, whole page examples (such as those in the D3 Gallery) and housing them in a component. That component can then be easily managed like any other in an Ext JS application.
The D3 Adapter then builds on these primitive base classes and provides some of the most commonly used D3 visualizations as ready to use components. These higher-level components understand your Ext JS data stores and connect them to D3 drawings and then ensure these stay up to date when your data changes.
Hierarchical Components
The need to display hierarchical information is quite common and D3 provides a wide range of visualizations for this type of data. These components connect an Ext.data.TreeStore to several of the popular D3 layouts to display hierarchical data.
Tree
The ‘d3-tree’ component is a perfect way to visualize hierarchical data as an actual tree in cases where the relative size of nodes is of little interest, and the focus is on the relative position of each node in the hierarchy. A horizontal tree makes for a more consistent look and more efficient use of space when text labels are shown next to each node.
Expand Code
JS Run
Ext.create('Ext.panel.Panel', {
renderTo: Ext.getBody(),
title: 'Tree Chart',
items: [
{
xtype: 'd3-tree',
store: {
type: 'tree',
data: [
{
text: "IT",
expanded: false,
children: [
{leaf: true, text: 'Norrin Radd'},
{leaf: true, text: 'Adam Warlock'}
]
},
{
text: "Engineering",
expanded: false,
children: [
{leaf: true, text: 'Mathew Murdoch'},
{leaf: true, text: 'Lucas Cage'}
]
},
{
text: "Support",
expanded: false,
children: [
{leaf: true, text: 'Peter Quill'}
]
}
]
},
interactions: {
type: 'panzoom',
zoom: {
extent: [0.3, 3],
doubleTap: false
}
},
nodeSize: [90, 50]
}
]
});
Sunburst
The ‘d3-sunburst’ component visualizes tree nodes as donut sectors, with the root circle in the center. The angle and area of each sector corresponds to its node value. By default the same value is returned for each node, meaning that siblings will span equal angles and occupy equal area.
Expand Code
JS Run
Ext.create('Ext.panel.Panel', {
renderTo: Ext.getBody(),
title: 'Sunburst Chart',
height: 750,
width: 750,
layout: 'fit',
items: [
{
xtype: 'd3-sunburst',
padding: 20,
tooltip: {
renderer: function (component, tooltip, record) {
tooltip.setHtml(record.get('text'));
}
},
store: {
type: 'tree',
data: [
{
text: "Oscorp",
children: [
{text: 'Norman Osborn'},
{text: 'Harry Osborn'},
{text: 'Arthur Stacy'}
]
},
{
text: "SHIELD",
children: [
{text: 'Nick Fury'},
{text: 'Maria Hill'},
{text: 'Tony Stark'}
]
},
{
text: "Illuminati",
children: [
{text: 'Namor'},
{text: 'Tony Stark'},
{text: 'Reed Richards'},
{text: 'Black Bolt'},
{text: 'Dr. Stephen Strange'},
{text: 'Charles Xavier'}
]
}
]
}
}
]
});
Pack
The ‘d3-pack’ component uses D3’s Pack Layout to visualize hierarchical data as an enclosure diagram. The size of each leaf node’s circle reveals a quantitative dimension of each data point. The enclosing circles show the approximate cumulative size of each subtree.
Expand Code
JS Run
Ext.create('Ext.panel.Panel', {
renderTo: Ext.getBody(),
title: 'Pack Chart',
height: 750,
width: 750,
layout: 'fit',
items: [
{
xtype: 'd3-pack',
tooltip: {
renderer: function (component, tooltip, record) {
tooltip.setHtml(record.get('text'));
}
},
store: {
type: 'tree',
data: [
{
"text": "DC",
"children": [
{
"text": "Flash",
"children": [
{ "text": "Flashpoint" }
]
},
{
"text": "Green Lantern",
"children": [
{ "text": "Rebirth" },
{ "text": "Sinestro Corps War" }
]
},
{
"text": "Batman",
"children": [
{ "text": "Hush" },
{ "text": "The Long Halloween" },
{ "text": "Batman and Robin" },
{ "text": "The Killing Joke" }
]
}
]
},
{
"text": "Marvel",
"children": [
{
"text": "All",
"children": [
{ "text": "Infinity War" },
{ "text": "Infinity Gauntlet" },
{ "text": "Avengers Disassembled" }
]
},
{
"text": "Spiderman",
"children": [
{ "text": "Ultimate Spiderman" }
]
},
{
"text": "Vision",
"children": [
{ "text": "The Vision" }
]
},
{
"text": "X-Men",
"children": [
{ "text": "Gifted" },
{ "text": "Dark Phoenix Saga" },
{ "text": "Unstoppable" }
]
}
]
}
]
}
}
]
});
TreeMap
The ‘d3-treemap’ component uses D3’s TreeMap Layout to recursively subdivide area into rectangles, where the area of any node in the tree corresponds to its value.
Expand Code
JS Run
Ext.create('Ext.panel.Panel', {
renderTo: Ext.getBody(),
title: 'TreeMap Chart',
height: 750,
width: 750,
layout: 'fit',
items: [
{
xtype: 'd3-treemap',
tooltip: {
renderer: function (component, tooltip, record) {
tooltip.setHtml(record.get('text'));
}
},
nodeValue: function (node) {
// Associates the rendered size of the box to a value in your data
return node.data.value;
},
store: {
type: 'tree',
data: [
{ text: 'Hulk',
value : 5,
children: [
{ text: 'The Leader', value: 3 },
{ text: 'Abomination', value: 2 },
{ text: 'Sandman', value: 1 }
]
},
{ text: 'Vision',
value : 4,
children: [
{ text: 'Kang', value: 4 },
{ text: 'Magneto', value: 3 },
{ text: 'Norman Osborn', value: 2 },
{ text: 'Anti-Vision', value: 1 }
]
},
{ text: 'Ghost Rider',
value : 3,
children: [
{ text: 'Mephisto', value: 1 }
]
},
{ text: 'Loki',
value : 2,
children: [
{ text: 'Captain America', value: 3 },
{ text: 'Deadpool', value: 4 },
{ text: 'Odin', value: 5 },
{ text: 'Scarlet Witch', value: 2 },
{ text: 'Silver Surfer', value: 1 }
]
},
{ text: 'Daredevil',
value : 1,
children: [
{ text: 'Purple Man', value: 4 },
{ text: 'Kingpin', value: 3 },
{ text: 'Namor', value: 2 },
{ text: 'Sabretooth', value: 1 }
]
}
]
}
}
]
});
HeatMap Component
Heatmaps are a great way to present three-dimensional data in a two-dimensional drawing. The HeatMap component maps X and Y values to axes as you might expect. It then maps Z values to a “color axis”.
Expand Code
JS Run
Ext.create('Ext.panel.Panel', {
renderTo: Ext.getBody(),
title: 'Heatmap Chart',
height: 750,
width: 750,
layout: 'fit',
items: [
{
xtype: 'd3-heatmap',
padding: {
top: 20,
right: 30,
bottom: 20,
left: 80
},
xAxis: {
axis: {
ticks: 'd3.timeDay',
tickFormat: "d3.timeFormat('%b %d')",
orient: 'bottom'
},
scale: {
type: 'time'
},
title: {
text: 'Date'
},
field: 'date',
step: 24 * 60 * 60 * 1000
},
yAxis: {
axis: {
orient: 'left'
},
scale: {
type: 'linear'
},
title: {
text: 'Total'
},
field: 'bucket',
step: 100
},
colorAxis: {
scale: {
type: 'linear',
range: ['white', 'orange']
},
field: 'count',
minimum: 0
},
tiles: {
attr: {
'stroke': 'black',
'stroke-width': 1
}
},
store: {
fields: [
{name: 'date', type: 'date', dateFormat: 'Y-m-d'},
'bucket',
'count'
],
data: [
{ "date": "2012-07-20", "bucket": 800, "count": 119 },
{ "date": "2012-07-20", "bucket": 900, "count": 123 },
{ "date": "2012-07-20", "bucket": 1000, "count": 173 },
{ "date": "2012-07-20", "bucket": 1100, "count": 226 },
{ "date": "2012-07-20", "bucket": 1200, "count": 284 },
{ "date": "2012-07-21", "bucket": 800, "count": 123 },
{ "date": "2012-07-21", "bucket": 900, "count": 165 },
{ "date": "2012-07-21", "bucket": 1000, "count": 237 },
{ "date": "2012-07-21", "bucket": 1100, "count": 278 },
{ "date": "2012-07-21", "bucket": 1200, "count": 338 },
{ "date": "2012-07-22", "bucket": 900, "count": 154 },
{ "date": "2012-07-22", "bucket": 1000, "count": 241 },
{ "date": "2012-07-22", "bucket": 1100, "count": 246 },
{ "date": "2012-07-22", "bucket": 1200, "count": 300 },
{ "date": "2012-07-22", "bucket": 1300, "count": 305 },
{ "date": "2012-07-23", "bucket": 800, "count": 120 },
{ "date": "2012-07-23", "bucket": 900, "count": 156 },
{ "date": "2012-07-23", "bucket": 1000, "count": 209 },
{ "date": "2012-07-23", "bucket": 1100, "count": 267 },
{ "date": "2012-07-23", "bucket": 1200, "count": 299 },
{ "date": "2012-07-23", "bucket": 1300, "count": 316 },
{ "date": "2012-07-24", "bucket": 800, "count": 105 },
{ "date": "2012-07-24", "bucket": 900, "count": 156 },
{ "date": "2012-07-24", "bucket": 1000, "count": 220 },
{ "date": "2012-07-24", "bucket": 1100, "count": 255 },
{ "date": "2012-07-24", "bucket": 1200, "count": 308 },
{ "date": "2012-07-25", "bucket": 800, "count": 104 },
{ "date": "2012-07-25", "bucket": 900, "count": 191 },
{ "date": "2012-07-25", "bucket": 1000, "count": 201 },
{ "date": "2012-07-25", "bucket": 1100, "count": 238 },
{ "date": "2012-07-25", "bucket": 1200, "count": 223 },
{ "date": "2012-07-26", "bucket": 1300, "count": 132 },
{ "date": "2012-07-26", "bucket": 1400, "count": 117 },
{ "date": "2012-07-26", "bucket": 1500, "count": 124 },
{ "date": "2012-07-26", "bucket": 1600, "count": 154 },
{ "date": "2012-07-26", "bucket": 1700, "count": 167 }
]
}
}
]
});
Note: “Heatmap.js” can throw up red flags for some ad blockers. If “Heatmap.js” won’t load while you’re developing, disable your ad blocker or create an exception for “Heatmap.js”.
Custom Components
Scenes
Ext.d3.svg.Svg
has a concept of a scene, which is represented by an SVG group (‘g’ element) that is designed to hold all rendered data nodes and other elements of the visualization.
For examples of a pure SVG scene example, see our Kitchen Sink.
Canvas
Ext.d3.canvas.Canvas deals with the Canvas element and its (‘2d’) context. Since D3 does not provide resolution independence for crisp looking drawings on HiDPI screens, it’s handled by the ‘d3-canvas’ component. HiDPI support is completely transparent. One just calls native Canvas APIs as they normally would. The HDPI overrides are automatically applied to the canvas context where necessary (see Ext.d3.canvas.HiDPI).
For examples of a pure Canvas example, see our Kitchen Sink.
Theming
Themes can be applied in the same fashion as other Ext JS components. However, there is an exception when theming tree nodes and heatmap cell colors. These will need an Ext.d3.axis.Color
.
In our above HeatMap example, You saw that the nodes/cells with the lowest ‘performance’ are white, while cells with the highest ‘performance’ are ‘orange’.
This representative gradient can be achieved with the following code:
colorAxis: {
field: 'performance',
scale: {
type: 'linear',
range: ['white', 'orange']
}
}
Events
D3 has it’s own way of dealing with node events. However, it is incompatible with the Ext JS event system. Additionally, the D3 event system does not automatically map touch events to desktop events and vice versa. Direct use of that event system is discouraged.
Instead, use one of the following approaches:
Preferred Method
var sceneElement = Ext.get(me.getScene().node()); // **preferred**
sceneElement.on(‘click’, handlerFn, scope, {
delegate: ‘g.x-class-name’
});
Fly Method
Ext.fly(selection.node()).on(‘click’, handlerFn, scope);
Each Method
selection.each(function (node) {
Ext.fly(this).on(‘click’, handlerFn, scope);
});
Note: D3 4.0 now features automatic event mapping. However, it may be incompatible with the Ext JS event system.
If it does become compatible, using two event systems in a single application is still discouraged.
Interactions
Instead of making use of D3’s “behaviors”, D3 components have a concept of interaction.
Currently only ‘panzoom’ interaction (Ext.d3.interaction.PanZoom) is supported. It is meant to replace and enhance the ‘zoom’ behavior provided by D3. For example, the ‘zoom’ behavior in D3 has no support for constrained panning, kinetic scrolling, scroll indicators and is generally incompatible with ExtJS event system. ‘Panzoom’ interaction fixes all that.
Note: Some of these features made it into D3 4.0, which was released summer 2016.
Tooltips
All high level components support the ‘tooltip’ config via the Ext.d3.mixin.ToolTip
mixin.
The tooltip config works just like in other ExtJS components except for one extra property — ‘renderer’. Renderer is generally the only thing that needs to be specified when configuring a tooltip. Here are two examples of how you might set up your tooltips.
View Controller Method
tooltip: { // inside a D3 component config in a view
renderer: 'onTooltip'
}
// view controller’s handler
onTooltip: function (component, tooltip, node, element, event) {
var n = node.childNodes.length;
tooltip.setHtml(n + ' items' + (n === 1 ? '' : 's') + ' inside.');
}
View Method
tooltip :
renderer: function(component, tooltip, record) {
tooltip.setHtml(record.get('text'));
}
}
Pivoting with D3
We can also aggregate local data using a Pivot Matrix. The new “pivot-d3” package contains several useful components that connect these pieces for you.
Ext.pivot.d3.HeatMap
This component produces a D3 HeatMap after the data is aggregated by the pivot matrix.
{
xtype: 'pivotheatmap',
// pivot matrix configurations
matrix: {
store: {
type: 'salesperemployee'
},
leftAxis: {
dataIndex: 'employee',
header: 'Employee',
sortable: false
},
topAxis: {
dataIndex: 'day',
sortIndex: 'dayNumber',
header: 'Day'
},
aggregate: {
dataIndex: 'sales',
aggregator: 'sum'
}
}
}
When the pivot matrix is configured there should be only one dimension per axis defined.
Ext.pivot.d3.TreeMap
This component produces a D3 TreeMap after the data is aggregated by the pivot matrix.
{
xtype: 'pivottreemap',
// pivot matrix configurations
matrix: {
store: {
type: 'salesperemployee'
},
leftAxis: {
dataIndex: 'employee',
header: 'Employee',
sortable: false
},
aggregate: {
dataIndex: 'sales',
aggregator: 'sum'
}
}
}
The topAxis
configuration of the pivot matrix is ignored, leftAxis
supports multiple dimensions and aggregate
should have only one dimension.
Ext.pivot.d3.Container
This new component connects the pivot Configurator plugin with any of the the above components to allow the end-user to configure the pivot matrix:
The Configurator can be configured using the “configurator” config:
{
xtype: 'pivotd3container',
drawing: {
xtype: 'something' // one of the above pivot D3 components
// more configs specific to that component
},
configurator: {
// pivotconfigurator plugin configs
fields: [{
dataIndex: 'person',
header: 'Person',
settings: {
// field settings
}
},{
// more fields
}]
},
// following configs are similar to the pivot grid
matrix: {
leftAxis: [],
topAxis: [],
aggregate: []
}
}