SenchaCon 2016: Add Magic to Your Ext JS Apps with D3 Visualizations - Vitaly Kravchenko
-
Upload
sencha -
Category
Technology
-
view
178 -
download
1
Transcript of SenchaCon 2016: Add Magic to Your Ext JS Apps with D3 Visualizations - Vitaly Kravchenko
Add Magic to Your ExtJS Apps with D3 Visualizations
Vitaly Kravchenko
D3 and Chartswe can have both
• Complements charts
• Offers unique components• Has no feature overlap
VS
D3 Componentsvisualized by themselves
Hierarchy Componentsfor tree store visualizations
d3-pack d3-tree d3-treemap d3-sunburst
So how did we create these charts?
That’s it!
then cycle through other x-types
items: {
xtype: 'd3-treemap',
}
•define the x-type of the D3 component
viewModel: vm,• define a view model
bind: { store: '{letters}' }
• bind component’s store to the store from a view model
The data is a tree of objects with
• the name field
•the children array
{ name: 'A', expanded: true, children: [ { name: 'B', expanded: true, children: [ { name: 'D' }, { name: 'E' } ] }, { name: 'C' } ]}
Data
items: {
xtype: 'd3-treemap',
}
viewModel: vm,
bind: { store: '{letters}' }
Config
...
{ name: 'D',},{ name: 'E'},
...
Data Result
How do we know to fetch the name?
• name and text are the defaults nodeText: ['name', 'text']
nodeText: 'foo'• but it can be anything
nodeText: function (component, node) { var record = node.data;
return record.get('firstName') + ' ' + record.get('lastName');}
• including a calculated value
• use the tooltip config
tooltip: {
}
When you can’t show everything
• it’s just a Ext.tip.ToolTip
• with the extra renderer property
renderer: function (cmp, tooltip, node) { tooltip.setHtml(node.data.get('hint')); }
showDelay: 500,
trackMouse: false
Data BindingviewModel: vm,
defaults: { bind: { store: '{letters}', selection: '{selection}', }, tooltip: { renderer: function (component, tooltip, node, element) { tooltip.setHtml(node.data.get('hint')); } }},
items: [ { xtype: 'd3-tree' }, { xtype: 'd3-pack' }, { xtype: 'd3-treemap' }, { xtype: 'd3-sunburst' }]
Live Demo
Make the size matter! { xtype: 'd3-treemap',
bind: { store: '{letters}' },
rootVisible: false,
nodeValue: 'frequency'}
[{ name: 'A', frequency: 8.167}, { name: 'B', frequency: 1.492}, { name: 'C', frequency: 2.782}, { name: 'D', frequency: 4.253}, { name: 'E', frequency: 12.702}]
{ xtype: 'd3-treemap',
bind: { store: '{letters}' },
rootVisible: false,
nodeValue: 1 // default}
Change the colors!
• use the colorAxis config
colorAxis: {
}
range
• field - what values to colorize
field: 'name',
• scale - how to do it
scale: { type: 'ordinal', range: 'd3.schemeCategory20c' }
domain
With a bit more tweaking…
Flexible coloring options
• custom scales (e.g. polylinear)
• custom logic
colorAxis: {
field: 'change',
scale: { type: 'linear',
domain: [ -5, 0, 5 ],
range: ['red', 'lightgray', 'green'] },
processor: function (axis, scale, node, field) {
var record = node.data;
return record.isLeaf()
? scale(record.get(field))
: 'lightgray'; // sector color }
}
Live Demo
Interactions
panzoom interaction
• it’s like zoom behavior with extras
• plays nice with Ext event/gesture system
• kinetic scrolling
• constraints, elastic borders
• scroll indicators
interactions: { type: 'panzoom'}
pan: { gesture: 'drag', constrain: true, momentum: { friction: 1, spring: 0.2 } },
zoom: { gesture: 'pinch', extent: [1, 3], uniform: true, mouseWheel: { factor: 1.02 }, doubleTap: { factor: 1.1 } }
HeatmapRepresent matrix values with colors
Sales per Employee per Day
{ "employee": "Alex", "day": "Monday", "sales": 67 }, { "employee": "Alex", "day": "Tuesday", "sales": 69 }, { "employee": "Alex", "day": "Wednesday", "sales": 187 }, { "employee": "Alex", "day": "Thursday", "sales": 62 }, { "employee": "Alex", "day": "Friday", "sales": 91 },
{ "employee": "Nige", "day": "Monday", "sales": 31 }, { "employee": "Nige", "day": "Tuesday", "sales": 164 }, { "employee": "Nige", "day": "Wednesday", "sales": 120 }, { "employee": "Nige", "day": "Thursday", "sales": 43 }, { "employee": "Nige", "day": "Friday", "sales": 32 },
Data
Heatmap
Heatmap definition
{ xtype: 'd3-heatmap',
store: { type: 'salesperemployee' },
...}
Component & Store
Heatmap definition
xAxis: { axis: { orient: 'bottom' }, scale: { type: 'band' }, title: { text: 'Employee', attr: { 'font-size': '14px' } }, field: 'employee' }
yAxis: { axis: { orient: 'left' }, scale: { type: 'band' }, title: { text: 'Day', attr: { 'font-size': '14px' } }, field: 'day' }
Axes
Heatmap definition
colorAxis: {
field: 'sales',
scale: { type: 'linear',
range: [ 'green’, 'yellow', 'red' ] } }
Colors
tiles: {
attr: { 'stroke': 'darkblue', 'stroke-width': 2 }
}
Styles
Heatmap definition
legend: {
docked: 'right', padding: 50,
items: { count: 7,
reverse: true,
size: { x: 60, y: 30 } }}
Legend
A Heatmap with Discrete X- and Y-axesName, Category, Index, etc.
Sales per Employee per Day
Discrete Color Axisis supported too
Axis and Legend configuration
colorAxis: {
scale: { type: 'ordinal', range: 'd3.schemeCategory20c' },
field: 'category'
}
legend: { docked: 'right', padding: 50,
items: { size: { x: 60, y: 30 } }}
Heatmaps with Continuous AxesQuantity, Time, etc.
Data
Heatmap
{ "date": "2012-07-20", "bucket": 800, "count": 89},{ "date": "2012-07-20", "bucket": 900, "count": 90},{ "date": "2012-07-20", "bucket": 1000, "count": 134}
{ "date": "2012-07-21", "bucket": 800, "count": 90},{ "date": "2012-07-21", "bucket": 900, "count": 129},{ "date": "2012-07-21", "bucket": 1000, "count": 192}
Purchases by Day
Heatmap definition
{ xtype: 'd3-heatmap',
store: { type: 'purchasesbyday' },
...}
Component & Store
Heatmap definition
yAxis: { axis: { orient: 'left', tickFormat: "d3.format('$d')" },
scale: { type: 'linear' }, title: { text: 'Total' }, field: 'bucket', step: 100}
y-Axis
Heatmap definition
xAxis: { axis: { orient: 'bottom', ticks: 'd3.timeDay', tickFormat: "d3.timeFormat('%b %d')" }, scale: { type: 'time' }, title: { text: 'Date' }, field: 'date', step: 24 * 60 * 60 * 1000}
x-Axis
Heatmap definition
colorAxis: {
field: 'count',
scale: { type: 'linear', range: ['white', 'green'] },
minimum: 0}
Colors
tiles: { attr: { 'stroke': 'green', 'stroke-width': 1 }}
Styles
Heatmap definition
legend: { docked: 'bottom', padding: 60, items: { count: 7, slice: [1], reverse: true, size: { x: 60, y: 30 } }}
Legend
Purchases by Day
Heatmap Tooltipsthey are just the same
tooltip: { renderer: 'onTooltip'}
onTooltip: function (component, tooltip, record, element, event) {
var xField = component.getXAxis().getField(), yField = component.getYAxis().getField(), colorField = component.getColorAxis().getField(),
date = record.get(xField), bucket = record.get(yField), count = record.get(colorField),
dateStr = Ext.Date.format(date, 'F j');
tooltip.setHtml(count + ' customers purchased a total of $' + bucket + ' to $' + (bucket + 100) + '<br> of goods on ' + dateStr);}
Live Demo
How D3 selections work?a quick aside
d3.select('body') // a selection (a transient object that holds the 'body' element)
d3.select('body').selectAll('div') // a selection of all 'div' elements in the body
// joining data with selected 'div' elements:var update = d3.select('body').selectAll('div').data([0, 1, 2, 3, 4])
// existing DOM elements in the selection// for which no new datum was found:update.exit()
// a selection of successfully updated DOM elements:update
// a selection with placeholder nodes// for data that has no corresponding DOM elements:update.enter()
update.enter().append('div')
<div>.__data__ = 0<div>.__data__ = 1
...<div>.__data__ = 4
How ExtJS Hierarchy components work?
var layout = d3.tree();
var layoutRoot = layout(d3.hierarchy(storeRoot));
var nodes = layoutRoot.descendants();
var update = scene.selectAll(‘.x-d3-node').data(nodes);
this.addNodes(update.enter());
this.updateNodes(update);
this.removeNodes(update.exit());
{ name: 'Art Landro’, url: '1.jpg',
children: [
{ name: 'Craig Gering', url: '4.jpg', },
...
{ xtype: 'd3-tree',
nodeSize: [200, 100], interactions: { type: 'panzoom', zoom: { doubleTap: false } },
store: { root: data }}
Subclassinglet’s create an org chart
Ext.define('Ext.d3.sencha.Tree', { extend: 'Ext.d3.hierarchy.tree.HorizontalTree',
xtype: 'sencha-tree',
...
});
Extending the tree
setupScene: function () { this.callParent(arguments);
this.getDefs().append('clipPath') .attr('id', 'node-clip') .append('circle') .attr('r', 45); }
Creating a clip path
addNodes: function (selection) { selection .attr('opacity', 0) .append('image') .attr('xlink:href', node => 'img/' + node.data.get('url')) .attr('x', '-45px') .attr('y', '-45px') .attr('width', '90px') .attr('height', '90px') .attr('clip-path', 'url(#node-clip)'); }
Populating entering nodes
updateNodes: function (update, enter) {
var selection = update.merge(enter);
selection .transition(this.layoutTransition) .attr('opacity', 1) .call(this.getNodeTransform());
}
Taking care of layout updates
{ name: 'Art Landro', url: '1.jpg',
children: [
{ name: 'Craig Gering', url: '4.jpg', },
...
{ xtype: 'sencha-tree',
nodeSize: [200, 100], interactions: { type: 'panzoom', zoom: { doubleTap: false } },
store: { root: data }}
Swapping the xtype
Live Demo
Custom visualizations
• d3-svg (aliased as d3)- creates an SVG document for you
- takes care about the size
- responds to store changes
- has a life cycle of a component
onSceneSetup: function (component, scene) { var data = ['A', 'B', 'C', 'D', ‘E', 'F', 'G', 'H', 'I', ‘J'],
color = d3.scaleOrdinal(d3.schemeCategory20c), selection = scene.selectAll().data(data).enter(), position = (d, i) => i * 30;
selection.append('circle') .attr('fill', d => color(d)) .attr('cx', position) .attr('r', 10);
selection.append('text') .text((d, i) => i < 5 ? d : '') .attr('x', position) .attr('y', 30) .attr('text-anchor', 'middle') .attr('font-weight', 'bold');}
{ xtype: 'd3',
listeners: { scenesetup: 'onSceneSetup' }}
• d3-canvas - same as d3, but for Canvas- resolution independence
What’s New?• D3 version 4.x based
- future proof
• Immutable selections- less unexpected side effects
• Immutable data- store data is not polluted by layout data,
due to separation between layout nodes and data
• New and Improved APIs- on both the Ext package and D3 library levels
• Better animations- FTW!
Some Breaking Changesnot our fault*
version 3 version 4
Some Breaking Changesfor example…
axis: { orient: 'bottom', ticks: 'd3.time.days', tickFormat: "d3.time.format('%b %d')"}
axis: { orient: 'bottom', ticks: 'd3.timeDay', tickFormat: "d3.timeFormat('%b %d')"}
Ext configs
d3.svg.axis() .orient('left') .ticks(d3.time.days) .tickFormat(d3.time.format('%b %d'));
d3.axisLeft()
.ticks(d3.timeDay) .tickFormat(d3.timeFormat('%b %d'));
D3 code
D3 v3.x D3 v4.x
Live Demothe last one