beyond-rails 0.0.189 → 0.0.190

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d267ba7a547c15723330eee12769f3902a397c29827ca1f86ca869ce532e0f95
4
- data.tar.gz: 9591c982bac2e9f92c4e6615f7220fe30b4c0801a3cee29c21529f45aacaa0e3
3
+ metadata.gz: eb585e6acd253f996d3786631eb1eaab59b3698491dc522044978444dbc5c827
4
+ data.tar.gz: 0dc1fc50d3272ad83151e1c17be5cd2c9e70cb16c253e0af435415073518352a
5
5
  SHA512:
6
- metadata.gz: 28c9d93b2579b5dfc806702ecacb468aecf10684a00068afd4e44552da0fecdda0b195d0b3d4fe08f877122df51febf9d0fe9730735b96661c812b1bfbca96aa
7
- data.tar.gz: 19b3c86011070fc942892147d0b57402798c1862fea976f58f01e9be431d82eca5b067739ae7078229007ab79aad8aacf0bd3a4e0889b8a74c1fb3da053b1620
6
+ metadata.gz: 949e96aff6039997362e3168548efc3b2a0b6bbb7dc100a1e178af903e6a587beea39586dbe482bd7f9d12d525894520a2a9528ec03921321f05eedb0690f33a
7
+ data.tar.gz: 77e87962fe997ebddde872f27b17315f31ebd3b1d9feede4b72cb47ac3e9f5815d32a8366e3efb51da02227031ab14c061185a8b0bc15ff8bbc9da4472ebfb6b
@@ -1,4 +1,4 @@
1
- import supportDom from '../utils/supportDom'
1
+ import supportDom from '../decorators/supportDom'
2
2
 
3
3
  @supportDom
4
4
  export default class Alert {
@@ -1,6 +1,6 @@
1
1
  import AutocompleteMenu from './AutocompleteMenu'
2
2
  import promisify from '../utils/promisify'
3
- import supportDom from '../utils/supportDom'
3
+ import supportDom from '../decorators/supportDom'
4
4
  import { debounce, noop } from '../utils'
5
5
 
6
6
  const defaultRenderMenuItem = row => {
@@ -1,5 +1,5 @@
1
1
  import getFloatedTargetPos from '../utils/getFloatedTargetPos'
2
- import supportDom from '../utils/supportDom'
2
+ import supportDom from '../decorators/supportDom'
3
3
  import { toPixel } from '../utils'
4
4
 
5
5
  @supportDom
@@ -0,0 +1,452 @@
1
+ import supportDom from '../decorators/supportDom'
2
+ import chartCommon from '../decorators/chartCommon'
3
+ import isDef from '../utils/isDef'
4
+ import isUndef from '../utils/isUndef'
5
+ import isInt from '../utils/isInt'
6
+ import { mem, range, sortBy, throttle, uniqBy } from '../utils'
7
+
8
+ const defaultBarStyles = [
9
+ '#5469d4',
10
+ '#7c54d4',
11
+ '#a254d4'
12
+ ]
13
+
14
+ @supportDom
15
+ @chartCommon
16
+ export default class BarChart {
17
+
18
+ constructor(dom, options = {}) {
19
+ this.dom = dom
20
+ this.options = options
21
+ this.bars = []
22
+
23
+ this.height = options.height || 186
24
+ this.width = options.width
25
+
26
+ this.toYLabel = isDef(options.toYLabel) ? mem(options.toYLabel) : (v => v)
27
+
28
+ this.xPadding = isDef(options.xPadding) ? options.xPadding : 20
29
+ this.yPadding = isDef(options.yPadding) ? options.yPadding : 20
30
+
31
+ this.yStep = options.yStep
32
+ this.yGutter = isDef(options.yGutter) ? options.yGutter : 10
33
+
34
+ this.xLabelMargin = isDef(options.xLabelMargin) ? options.xLabelMargin : 10
35
+ this.yLabelMargin = isDef(options.yLanelMargin) ? options.yLabelMargin : 14
36
+
37
+ this.fontSize = options.fontSize || 12
38
+ this.bgColor = options.bgColor || '#fff'
39
+ this.barStyles = options.barStyles || defaultBarStyles
40
+
41
+ this.yLabelRows = []
42
+ this.barPosMap = new Map()
43
+
44
+ this.init()
45
+ }
46
+
47
+ init() {
48
+ this.setDpr()
49
+ this.setDomWidthIfNeeded()
50
+ this.setCanvas()
51
+ this.clear()
52
+ this.bindMedia()
53
+ this.bindBarVisible()
54
+ }
55
+
56
+ get contentWidth() {
57
+ return this.width - (this.xPadding * 2) - this.yLabelMargin -
58
+ this.yLabelWidth - this.halfXLabelWidth
59
+ }
60
+
61
+ get contentHeight() {
62
+ return this.height - (this.yPadding * 2) - this.xLabelMargin - this.xLabelHeight
63
+ }
64
+
65
+ get firstY() {
66
+ return this.yLabelRows[0].value
67
+ }
68
+
69
+ get halfXLabelWidth() {
70
+ return this.xLabelWidth / 2
71
+ }
72
+
73
+ get halfYLabelHeight() {
74
+ return this.yLabelHeight / 2
75
+ }
76
+
77
+ get xAxisStart() {
78
+ return this.xPadding + this.halfXLabelWidth
79
+ }
80
+
81
+ get xAxisEnd() {
82
+ return this.width - this.xPadding - this.yLabelWidth -
83
+ this.yLabelMargin - this.halfXLabelWidth
84
+ }
85
+
86
+ get yAxisStart() {
87
+ return this.height - this.yPadding - this.xLabelHeight -
88
+ this.xLabelMargin + this.halfYLabelHeight
89
+ }
90
+
91
+ get yAxisEnd() {
92
+ return this.yAxisStart - this.contentHeight
93
+ }
94
+
95
+ get lastY() {
96
+ const { yLabelRows } = this
97
+ return yLabelRows[yLabelRows.length - 1].value
98
+ }
99
+
100
+ get yRatio() {
101
+ const lineHeight = this.yAxisStart - this.yAxisEnd
102
+ const yDelta = Math.abs(this.lastY - this.firstY)
103
+ return yDelta / lineHeight
104
+ }
105
+
106
+ bindBarVisible() {
107
+ if (isUndef(this.options.onBarMouseOver)) {
108
+ return
109
+ }
110
+ if (! ('onmousemove' in this.canvas)) {
111
+ return
112
+ }
113
+ this.addLayer()
114
+ const canvas = this.getHighestCanvas()
115
+ this.addEvent(canvas, 'mousemove', throttle(this.handleMouseMove.bind(this), 30))
116
+ }
117
+
118
+ clearBarPos() {
119
+ this.barPosMap.clear()
120
+ }
121
+
122
+ draw() {
123
+ this.clear()
124
+ this.drawXAxis()
125
+ this.drawYAxis()
126
+ this.drawBgLines()
127
+ this.drawBars()
128
+ }
129
+
130
+ drawBars() {
131
+ const { barPosMap, barStyles, ctx, xLabelRows } = this
132
+ xLabelRows.forEach((row, i) => {
133
+ const pos = barPosMap.get(row)
134
+ if (pos) {
135
+ const { x, y, width, height } = pos
136
+ ctx.fillStyle = barStyles[i] || '#000'
137
+ ctx.fillRect(x, y, width, height)
138
+ }
139
+ })
140
+ }
141
+
142
+ drawBgLines() {
143
+
144
+ const { ctx, yLabelRows, firstY, yAxisStart, yRatio } = this
145
+ const xStart = this.xPadding
146
+ const xEnd = this.width - this.xPadding - this.yLabelWidth - this.yLabelMargin
147
+
148
+ ctx.strokeStyle = 'rgba(224, 224, 224, .5)'
149
+ ctx.lineWidth = 1
150
+
151
+ yLabelRows.forEach(row => {
152
+
153
+ const y = yAxisStart - ((row.value - firstY) / yRatio)
154
+
155
+ ctx.beginPath()
156
+ ctx.moveTo(xStart, y)
157
+ ctx.lineTo(xEnd, y)
158
+ ctx.stroke()
159
+ ctx.closePath
160
+ })
161
+ }
162
+
163
+ drawXAxis() {
164
+ const { ctx, xLabelRows, xAxisStart, xAxisEnd } = this
165
+ const totalWidth = xLabelRows.reduce((w, row) => w + row.length, 0)
166
+ const contentWidth = xAxisEnd - xAxisStart
167
+ const gutter = (contentWidth - totalWidth) / (xLabelRows.length - 1)
168
+ const y = this.height - this.yPadding
169
+ let x = xAxisStart
170
+
171
+ ctx.textBaseline = 'top'
172
+ ctx.fillStyle = '#3c4257'
173
+
174
+ xLabelRows.forEach((row, i) => {
175
+ ctx.fillText(row.label, x, y)
176
+ x += (row.length + gutter)
177
+ })
178
+ }
179
+
180
+ drawYAxis() {
181
+ const { ctx, firstY, yLabelRows, yAxisStart, yRatio, yLabelHeight } = this
182
+ const x = this.width - this.xPadding
183
+ const delta = yLabelHeight * .45
184
+
185
+ ctx.fillStyle = '#3c4257'
186
+ ctx.textAlign = 'right'
187
+
188
+ yLabelRows.forEach(row => {
189
+ const y = yAxisStart - ((row.value - firstY) / yRatio)
190
+ ctx.fillText(row.label, x, y - delta)
191
+ })
192
+ }
193
+
194
+ findMouseOverBarPos(canvasMousePos) {
195
+ const { barPosMap, xLabelRows } = this
196
+ const { x: mouseX, y: mouseY } = canvasMousePos
197
+ let index = 0
198
+ for (const row of xLabelRows) {
199
+ const pos = barPosMap.get(row)
200
+ const { x, y, width, height } = pos
201
+ if ((x <= mouseX) && (mouseX <= (x + width)) &&
202
+ (y <= mouseY) && (mouseY <= (y + height))) {
203
+ return { index, row, pos }
204
+ }
205
+ ++index
206
+ }
207
+ }
208
+
209
+ drawBarGlow(res) {
210
+ this.clearBarGlow()
211
+ const ctx = this.firstLayer.canvas.getContext('2d')
212
+ ctx.save()
213
+ const { x, y, width, height } = res.pos
214
+ const glowWidth = width * 1.4
215
+ const glowHeight = ((glowWidth - width) / 2) + height
216
+ const glowX = x - ((glowWidth - width) / 2)
217
+ const glowY = y - (glowHeight - height)
218
+ ctx.globalAlpha = 0.2
219
+ ctx.fillStyle = this.barStyles[res.index]
220
+ ctx.fillRect(glowX, glowY, glowWidth, glowHeight)
221
+ ctx.restore()
222
+ }
223
+
224
+ clearBarGlow() {
225
+ const ctx = this.firstLayer.canvas.getContext('2d')
226
+ ctx.clearRect(0, 0, this.width, this.height)
227
+ }
228
+
229
+ handleMouseMove(event) {
230
+ const canvasMousePos = this.getMousePosInCanvas(event)
231
+ const mouseOverRes = this.findMouseOverBarPos(canvasMousePos)
232
+ const { lastMouseOverRes } = this
233
+
234
+ // don't repaint the same index
235
+ if (lastMouseOverRes && mouseOverRes && (lastMouseOverRes.index === mouseOverRes.index)) {
236
+ return
237
+ }
238
+
239
+ // don't re-clear
240
+ if (isUndef(mouseOverRes) && isUndef(lastMouseOverRes)) {
241
+ return
242
+ }
243
+
244
+ if (mouseOverRes) {
245
+ this.drawBarGlow(mouseOverRes)
246
+ }
247
+ else {
248
+ this.clearBarGlow()
249
+ }
250
+ this.lastMouseOverRes = mouseOverRes
251
+ const mousePos = this.getMousePos(canvasMousePos)
252
+ const res = this.getBarMouseOverRes(mouseOverRes)
253
+ this.options.onBarMouseOver(mousePos, res)
254
+ }
255
+
256
+ getBarMouseOverRes(res) {
257
+ if (res) {
258
+ return {
259
+ index: res.index,
260
+ bar: res.row
261
+ }
262
+ }
263
+ }
264
+
265
+ getUniqSortedBars() {
266
+ const bars = uniqBy(this.bars, 'value')
267
+ return sortBy(bars, ['value'])
268
+ }
269
+
270
+ getXLabelRows() {
271
+ const { ctx } = this
272
+ return this.bars.map(bar => {
273
+ const { label } = bar
274
+ return {
275
+ label,
276
+ length: ctx.measureText(label).width,
277
+ value: bar.value
278
+ }
279
+ })
280
+ }
281
+
282
+ getYLabelRows() {
283
+
284
+ const { contentHeight } = this
285
+ const gutter = this.yGutter
286
+ const toLabel = this.toYLabel
287
+ const measureLength = () => this.fontSize
288
+
289
+ const bars = this.getUniqSortedBars()
290
+ const firstBar = bars[0]
291
+ const lastBar = bars[bars.length - 1]
292
+
293
+ if (bars.length <= 2) {
294
+ return bars.map(bar => {
295
+ const value = bar.value
296
+ const label = toLabel(value)
297
+ const length = measureLength()
298
+ return { label, length, value }
299
+ })
300
+ }
301
+
302
+ const firstBarValue = firstBar.value
303
+ const lastBarValue = lastBar.value
304
+ const barCount = bars.length
305
+
306
+ let step = this.yStep
307
+
308
+ if (isUndef(step)) {
309
+ step = this.getAutoStep(firstBarValue, lastBarValue, barCount)
310
+ }
311
+
312
+ const [stepStart, stepEnd] = this.getStepStartEnd(step, firstBarValue, lastBarValue)
313
+ const values = range(stepStart, stepEnd + step, step)
314
+ .map(value => {
315
+ return isInt(value) ? value : parseFloat(value.toFixed(2))
316
+ })
317
+
318
+ const valueCount = values.length
319
+ const initialGap = parseInt((valueCount - 2) / 2, 10)
320
+
321
+ const firstValue = values[0]
322
+ const lastValue = values[valueCount - 1]
323
+ const firstLabel = toLabel(firstValue)
324
+ const lastLabel = toLabel(lastValue)
325
+
326
+ let stepRows = [
327
+ { label: firstLabel, length: measureLength(), value: firstValue },
328
+ { label: lastLabel, length: measureLength(), value: lastValue },
329
+ ]
330
+
331
+ for (let gap = initialGap; gap >= 0; gap--) {
332
+ const { lengthTotal, rows } = this.getLengthTotalData(gap, gutter, values, measureLength, toLabel)
333
+ if (lengthTotal <= contentHeight) {
334
+ stepRows = rows
335
+ continue
336
+ }
337
+ return stepRows
338
+ }
339
+ return stepRows
340
+ }
341
+
342
+ refresh() {
343
+ this.raf(() => {
344
+ this.clearBarPos()
345
+ this.setDomWidthIfNeeded()
346
+ this.setCanvasSize(this.canvas)
347
+ this.setLabelWidths()
348
+ this.setLabelHeights()
349
+ this.setAxisData()
350
+ this.setBarPos()
351
+ this.draw()
352
+ })
353
+ }
354
+
355
+ setAxisData() {
356
+ this.xLabelRows = this.getXLabelRows()
357
+ this.yLabelRows = this.getYLabelRows()
358
+ }
359
+
360
+ setBarPos() {
361
+ const barWidth = 45
362
+ const halfBarWidth = parseInt(barWidth / 2, 10)
363
+ const { barPosMap, firstY, xLabelRows, xAxisStart, xAxisEnd, yAxisStart, yRatio } = this
364
+ const totalWidth = xLabelRows.reduce((w, row) => w + row.length, 0)
365
+ const contentWidth = xAxisEnd - xAxisStart
366
+ const gutter = (contentWidth - totalWidth) / (xLabelRows.length - 1)
367
+
368
+ const { centerPoints } = xLabelRows.reduce((res, row, i) => {
369
+ const { x } = res
370
+ const centerPoints = res.centerPoints.slice()
371
+ const width = row.length
372
+ const halfWidth = parseInt(width / 2, 10)
373
+ centerPoints.push(x + halfWidth)
374
+ return {
375
+ x: x + (width + gutter),
376
+ centerPoints
377
+ }
378
+ }, {
379
+ x: xAxisStart,
380
+ centerPoints: []
381
+ })
382
+
383
+ xLabelRows.forEach((row, i) => {
384
+ const barHeight = (row.value - firstY) / yRatio
385
+ const centerPoint = centerPoints[i]
386
+ const x = centerPoint - halfBarWidth
387
+ const y = yAxisStart - barHeight
388
+ const pos = { x, y, width: barWidth, height: barHeight }
389
+ barPosMap.set(row, pos)
390
+ })
391
+ }
392
+
393
+ setData(bars) {
394
+ this.clearBarPos()
395
+ this.bars = bars
396
+ this.setLabelWidths()
397
+ this.setLabelHeights()
398
+ this.setAxisData()
399
+ this.setBarPos()
400
+ this.raf(() => this.draw())
401
+ }
402
+
403
+ setLabelWidths() {
404
+
405
+ const { toYLabel, ctx } = this
406
+ const res = this.bars.filter(bar => bar)
407
+ .reduce((o, bar) => {
408
+
409
+ const { xLabelWidth, yLabelWidth } = o
410
+ const measuredXLabelWidth = ctx.measureText(bar.label).width
411
+ const measuredYLabelWidth = ctx.measureText(toYLabel(bar.value)).width
412
+
413
+ return {
414
+ xLabelWidth: (measuredXLabelWidth > xLabelWidth) ? measuredXLabelWidth : xLabelWidth,
415
+ yLabelWidth: (measuredYLabelWidth > yLabelWidth) ? measuredYLabelWidth : yLabelWidth,
416
+ }
417
+ }, {
418
+ xLabelWidth: 0,
419
+ yLabelWidth: 0
420
+ })
421
+
422
+ this.xLabelWidth = res.xLabelWidth
423
+ this.yLabelWidth = res.yLabelWidth
424
+ }
425
+
426
+ setLabelHeights() {
427
+ this.xLabelHeight = this.fontSize
428
+ this.yLabelHeight = this.fontSize
429
+ }
430
+
431
+ handleDprChange() {
432
+ this.setDpr()
433
+ this.refresh()
434
+ }
435
+
436
+ destroy() {
437
+ const { dom, canvas } = this
438
+ const { toYLabel } = this.options
439
+
440
+ if (isDef(toYLabel)) {
441
+ mem.clear(this.toYLabel)
442
+ }
443
+ this.clearBarPos()
444
+ this.unbindMedia()
445
+ this.removeAllLayers()
446
+
447
+ if (dom.contains(canvas)) {
448
+ dom.removeChild(canvas)
449
+ dom.style.removeProperty('position')
450
+ }
451
+ }
452
+ }