-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathSmartLegend.js
348 lines (289 loc) · 10.6 KB
/
SmartLegend.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
/*
* Smart chart Legend: it tries hard to fit available Chart dimensions
* without hogging all the space. Will rearrange itself in columns or rows.
*
* Uses Smart legend item to provide explicit Series field titles, too.
*
* Version 0.99, compatible with Ext JS 4.1.
*
* Copyright (c) 2011-2012 Alexander Tokarev.
*
* Usage: drop-in replacement for Ext.chart.Legend. See demo application
* for more details.
*
* This code is licensed under the terms of the Open Source LGPL 3.0 license.
* Commercial use is permitted to the extent that the code/component(s) do NOT
* become part of another Open Source or Commercially licensed development library
* or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/
Ext.define('Ext.ux.chart.SmartLegend', {
extend: 'Ext.ux.chart.Legend',
requires: [ 'Ext.ux.chart.SmartLegendItem' ],
/*
* @cfg {String} seriesType Allows to trick Legend items into drawing
* their markers with a style that differs from default series style;
* like box marker for line or line marker for pie, etc.
*/
/**
* @private Create all the sprites for the legend
*/
create: function() {
var me = this,
items = me.chart.series.items;
// Avoid re-creation when Legend is invisible
if ( me.rebuild || (!me.created && me.isDisplayed()) ) {
me.createBox();
me.createItems();
me.updatePosition(true);
};
if (!me.created && me.isDisplayed()) {
me.created = true;
// Listen for changes to series titles to trigger regeneration of the legend
for ( var i = 0, l = items.length; i < l; i++ ) {
var series = items[i];
series.on('titlechange', me.redraw, me);
};
}
},
/**
* @private Get the bounds for the legend's outer box
*/
getBBox: function() {
var me = this,
mround = Math.round;
return {
x: mround(me.x - me.boxStrokeWidth / 2),
y: mround(me.y - me.boxStrokeWidth / 2),
width: mround(me.width),
height: mround(me.height)
};
},
/**
* @private Update the position of all the legend's sprites to match its current x/y values
*/
updatePosition: function(init) {
var me = this,
items = me.items,
pos, myBBox, bsBBox;
if ( !me.isDisplayed() ) return;
// Find the position based on the dimensions
pos = me.calcPosition();
myBBox = me.getBBox();
bsBBox = me.boxSprite.getBBox();
// We assume that if dimensions and box are current,
// there is no need to update.
if ( !init &&
me.x == pos.x && me.y == pos.y &&
myBBox.x == bsBBox.x && myBBox.y == bsBBox.y &&
myBBox.height == bsBBox.height &&
myBBox.width == bsBBox.width )
{
return;
};
me.x = myBBox.x = pos.x;
me.y = myBBox.y = pos.y;
// Update the position of each item
for ( var i = 0, l = items.length; i < l; i++ ) {
items[i].updatePosition();
};
// Update the position of the outer box
me.boxSprite.setAttributes(myBBox, true);
},
/**
* @private Creates single Legend Item.
*/
createLegendItem: function(series, yFieldIndex) {
var me = this,
conf;
conf = {
legend: me,
series: series,
surface: me.chart.surface,
yFieldIndex: yFieldIndex
};
if ( me.seriesType !== undefined ) {
Ext.apply(conf, {
seriesType: me.seriesType
});
};
return new Ext.ux.chart.SmartLegendItem(conf);
},
/**
* @private Works around missing getInsets in Ext.chart.Chart
*/
getChartInsets: function() {
var me = this,
chart = me.chart,
ip = chart.insetPadding;
return chart.getInsets ? chart.getInsets()
: { left: ip, top: ip, right: ip, bottom: ip }
;
},
/**
* @private Calculates Legend position with respect to Chart elements.
*/
calcPosition: function() {
var me = this,
chartBBox = me.chart.chartBBox,
surface = me.chart.surface,
mfloor = Math.floor,
insets, cWidth, lWidth, cHeight, lHeight, sWidth, sHeight,
aWidth, aHeight, x, y, haveWidth, haveHeight;
// Support for refactored Chart
insets = me.getChartInsets();
sWidth = surface.width;
sHeight = surface.height;
cWidth = chartBBox.width;
cHeight = chartBBox.height;
lWidth = me.width;
lHeight = me.height;
// A is for "available"
aWidth = sWidth - insets.left - insets.right;
aHeight = sHeight - insets.top - insets.bottom;
// Legend either fits between insets or it doesn't,
// in which case we don't care to place it accurately enough
haveWidth = aWidth >= lWidth;
haveHeight = aHeight >= lHeight;
// Find position based on dimensions
// Take into account that insets can be different on sides
switch ( me.position ) {
case 'left':
x = insets.left;
y = haveHeight ? mfloor(insets.top + aHeight / 2 - lHeight / 2)
: mfloor(sHeight / 2 - lHeight / 2)
;
break;
case 'right':
x = mfloor(sWidth - lWidth) - insets.right;
y = haveHeight ? mfloor(insets.top + aHeight / 2 - lHeight / 2)
: mfloor(sHeight / 2 - lHeight / 2)
;
break;
case 'top':
x = haveWidth ? mfloor(insets.left + aWidth / 2 - lWidth / 2)
: mfloor(sWidth / 2 - lWidth / 2)
;
y = insets.top;
break;
case 'bottom':
x = haveWidth ? mfloor(insets.left + aWidth / 2 - lWidth / 2)
: mfloor(sWidth / 2 - lWidth / 2)
;
y = mfloor(sHeight - lHeight) - insets.bottom;
break;
default:
x = mfloor(me.origX) + insets.left;
y = mfloor(me.origY) + insets.top;
};
return { x: x, y: y };
},
/**
* @private Calculates maximum available box for Legend.
*/
getMaximumBBox: function() {
var me = this,
surface = me.chart.surface,
insets, chartWidth, chartHeight;
insets = me.getChartInsets();
chartWidth = surface.width - insets.left - insets.right;
chartHeight = surface.height - insets.top - insets.bottom;
return { width: chartWidth, height: chartHeight };
},
/**
* @private Positions all items within Legend box.
*/
alignItems: function() {
var me = this,
items = me.items,
numItems = me.items.length,
padding = me.padding,
itemSpacing = me.itemSpacing,
vertical = me.isVertical,
mceil = Math.ceil,
mfloor = Math.floor,
mmax = Math.max,
x, y, bbox, height, width, dim;
dim = me.updateItemDimensions();
var maxWidth = dim.maxWidth,
maxHeight = dim.maxHeight,
totalWidth = dim.totalWidth,
totalHeight = dim.totalHeight,
spacing = dim.spacing;
bbox = me.getMaximumBBox();
// Ugh. All this verboseness is just because there is no list context assignment.
// JS sucks.
var totalDim = vertical ? totalHeight : totalWidth,
stuffInto = vertical ? bbox.height : bbox.width;
// Account for padding
stuffInto -= padding * 2;
// I call them lines because they can be either columns or rows
var lines, perLine, fract;
if ( totalDim < stuffInto ) {
lines = 1;
perLine = numItems;
}
else {
lines = mceil(totalDim / stuffInto);
fract = (totalDim / stuffInto) - mfloor(totalDim / stuffInto);
lines += fract > 0.8 ? 1 : 0;
perLine = mceil(numItems / lines);
};
// Position the items
for ( var line = 0; line < lines; line++ ) {
var cumulative = 0;
for ( var cursor = 0; cursor < perLine; cursor++ ) {
var item = items[ (line*perLine) + cursor ];
if ( item === undefined ) {
continue;
};
x = vertical ? line * (maxWidth + spacing)
: cursor * (maxWidth + spacing)
;
y = vertical ? cursor * (maxHeight + spacing)
: line * (maxHeight + spacing)
;
item.x = x + padding;
item.y = y + (vertical ? padding + maxHeight / 2 : padding + maxHeight / 2);
};
};
// Calculate legend box dimensions
me.width = vertical ? lines * (maxWidth + spacing) - spacing
: perLine * (maxWidth + spacing) - spacing
;
me.width += padding * 2;
me.height = vertical ? perLine * (maxHeight + spacing) - spacing
: lines * (maxHeight + spacing) - spacing
;
me.height += padding * 2;
me.itemHeight = maxHeight;
},
/**
* @private Toggles Legend visibility.
*/
toggleVisibility: function(visible) {
var me = this,
items = me.items;
me.visible = visible;
for ( var i = 0, l = items.length; i < l; i++ ) {
var item = items[i];
visible ? item.show(true) : item.hide(true);
};
visible ? me.boxSprite.show(true) : me.boxSprite.hide(true);
},
/**
* @private Shows the Legend.
*/
show: function() {
var me = this;
me.toggleVisibility(true);
},
/**
* @private Hides the Legend.
*/
hide: function() {
var me = this;
me.toggleVisibility(false);
}
});