beyond-rails 0.0.188 → 0.0.193

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/src/js/components/Alert.js +1 -1
  3. data/src/js/components/Autocomplete.js +1 -1
  4. data/src/js/components/AutocompleteMenu.js +1 -1
  5. data/src/js/components/BarChart.js +452 -0
  6. data/src/js/components/Btn.js +1 -1
  7. data/src/js/components/Checkbox.js +1 -1
  8. data/src/js/components/DateInput.js +1 -1
  9. data/src/js/components/DateMenu.js +1 -1
  10. data/src/js/components/DateTimeRanger.js +1 -1
  11. data/src/js/components/Datepicker.js +1 -1
  12. data/src/js/components/DatepickerBtnArrow.js +1 -1
  13. data/src/js/components/Dropdown.js +1 -1
  14. data/src/js/components/LineChart.js +535 -0
  15. data/src/js/components/Menu.js +1 -1
  16. data/src/js/components/Modal.js +76 -31
  17. data/src/js/components/Navbar.js +1 -1
  18. data/src/js/components/Radio.js +1 -1
  19. data/src/js/components/SearchDropdown.js +1 -1
  20. data/src/js/components/Sidebar.js +1 -1
  21. data/src/js/components/Tabbox.js +1 -1
  22. data/src/js/components/TimeInput.js +1 -1
  23. data/src/js/components/TimeMenu.js +1 -1
  24. data/src/js/components/Timepicker.js +1 -1
  25. data/src/js/components/Toast.js +1 -1
  26. data/src/js/components/ToastItem.js +1 -1
  27. data/src/js/components/Tooltip.js +1 -1
  28. data/src/js/decorators/chartCommon.js +218 -0
  29. data/src/js/{utils → decorators}/supportDom.js +1 -1
  30. data/src/js/index.js +6 -2
  31. data/src/js/jquery/bindModalFn.js +57 -6
  32. data/src/js/utils/index.js +10 -1
  33. data/src/js/utils/isDef.js +3 -0
  34. data/src/js/utils/isInt.js +3 -0
  35. data/src/js/utils/isUndef.js +3 -0
  36. data/src/sass/_beyond.scss +1 -0
  37. data/src/sass/components/_breadcrumb.scss +4 -4
  38. data/src/sass/components/_chart.scss +14 -0
  39. data/src/sass/components/_mega-menu.scss +22 -28
  40. data/src/sass/components/_nav.scss +1 -0
  41. metadata +28 -9
@@ -1,4 +1,4 @@
1
- import supportDom from '../utils/supportDom'
1
+ import supportDom from '../decorators/supportDom'
2
2
  import { toPixel } from '../utils'
3
3
 
4
4
  @supportDom
@@ -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 Checkbox {
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_TIMEZONE } from '../consts'
2
- import supportDom from '../utils/supportDom'
2
+ import supportDom from '../decorators/supportDom'
3
3
  import isTouchDevice from '../utils/isTouchDevice'
4
4
  import { format } from '../utils'
5
5
 
@@ -2,7 +2,7 @@ import getFloatedTargetPos from '../utils/getFloatedTargetPos'
2
2
  import isTouchDevice from '../utils/isTouchDevice'
3
3
  import dateLt from '../utils/dateLt'
4
4
  import dateEq from '../utils/dateEq'
5
- import supportDom from '../utils/supportDom'
5
+ import supportDom from '../decorators/supportDom'
6
6
  import {
7
7
  addDays,
8
8
  addMonths,
@@ -5,7 +5,7 @@ import TimeMenu from './TimeMenu'
5
5
  import DatepickerBtnArrow from './DatepickerBtnArrow'
6
6
  import dateGt from '../utils/dateGt'
7
7
  import dateLt from '../utils/dateLt'
8
- import supportDom from '../utils/supportDom'
8
+ import supportDom from '../decorators/supportDom'
9
9
  import {
10
10
  endOfDay,
11
11
  dateToTimestamp,
@@ -2,7 +2,7 @@ import DateInput from './DateInput'
2
2
  import TimeInput from './TimeInput'
3
3
  import DateMenu from './DateMenu'
4
4
  import TimeMenu from './TimeMenu'
5
- import supportDom from '../utils/supportDom'
5
+ import supportDom from '../decorators/supportDom'
6
6
  import { DEFAULT_TIMEZONE } from '../consts'
7
7
  import {
8
8
  dateToTimestamp,
@@ -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 DatepickerBtnArrow {
@@ -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 { isFunction, toPixel, throttle } from '../utils'
4
4
 
5
5
  @supportDom
@@ -0,0 +1,535 @@
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 { mem, range, sortBy, throttle, uniqBy } from '../utils'
6
+
7
+ /**
8
+ * -------------------------------------------------------------------------------------------------
9
+ * ^ |
10
+ * | |
11
+ * yPadding |
12
+ * | |
13
+ * v |
14
+ * ----------------------------------------------------------- yLabel |
15
+ * ----------------------------------------------------------- yLabel |
16
+ * <- xPadding -> ----------------------------------------------------------- yLabel <- xPadding -> |
17
+ * ----------------------------------------------------------- yLabel |
18
+ * ^ |
19
+ * yGutter |
20
+ * v |
21
+ * ----------------------------------------------------------- yLabel |
22
+ * xLabel <- xGutter -> xLabel <- xGutter -> xLabel |
23
+ * ^ |
24
+ * | |
25
+ * yPadding |
26
+ * | |
27
+ * v |
28
+ * --------------------------------------------------------------------------------------------------
29
+ **/
30
+ const defaultLineStyles = [
31
+ '#5469d4',
32
+ '#7c54d4',
33
+ '#a254d4'
34
+ ]
35
+
36
+ @supportDom
37
+ @chartCommon
38
+ export default class LineChart {
39
+
40
+ constructor(dom, options = {}) {
41
+ this.dom = dom
42
+ this.options = options
43
+ this.pointsArr = []
44
+ this.height = options.height || 150
45
+ this.width = options.width
46
+
47
+ this.toXLabel = isDef(options.toXLabel) ? mem(options.toXLabel) : (v => v)
48
+ this.toYLabel = isDef(options.toYLabel) ? mem(options.toYLabel) : (v => v)
49
+
50
+ this.xPadding = isDef(options.xPadding) ? options.xPadding : 20
51
+ this.yPadding = isDef(options.yPadding) ? options.yPadding : 20
52
+
53
+ this.xGutter = isDef(options.xGutter) ? options.xGutter : 100
54
+ this.yGutter = isDef(options.yGutter) ? options.yGutter : 10
55
+
56
+ this.xLabelMargin = isDef(options.xLabelMargin) ? options.xLabelMargin : 10
57
+ this.yLabelMargin = isDef(options.yLanelMargin) ? options.yLabelMargin : 10
58
+
59
+ this.lineStyles = options.lineStyles || defaultLineStyles
60
+
61
+ this.bgColor = options.bgColor || '#fff'
62
+ this.fontSize = options.fontSize || 12
63
+
64
+ this.xStep = options.xStep
65
+ this.yStep = options.yStep
66
+
67
+ this.lineLabels = options.lineLabels || []
68
+ this.lineLabelMargin = isDef(options.lineLabelMargin) ? options.lineLabelMargin : 20
69
+
70
+ this.pointPosMap = new Map()
71
+ this.xLabelRows = []
72
+ this.yLabelRows = []
73
+
74
+ this.init()
75
+ }
76
+
77
+ init() {
78
+ this.setDpr()
79
+ this.setDomWidthIfNeeded()
80
+ this.setCanvas()
81
+ this.clear()
82
+ this.bindMedia()
83
+ this.bindPointMouseOver()
84
+ }
85
+
86
+ get contentWidth() {
87
+ return this.width - (this.xPadding * 2) - this.yLabelMargin -
88
+ this.yLabelWidth - (this.xLabelWidth / 2)
89
+ }
90
+
91
+ get contentHeight() {
92
+ return this.height - (this.yPadding * 2) - this.xLabelMargin -
93
+ this.xLabelHeight - this.lineLabelBoxHeight
94
+ }
95
+
96
+ get firstX() {
97
+ return this.xLabelRows[0].value
98
+ }
99
+
100
+ get lastX() {
101
+ const { xLabelRows } = this
102
+ return xLabelRows[xLabelRows.length - 1].value
103
+ }
104
+
105
+ get firstY() {
106
+ return this.yLabelRows[0].value
107
+ }
108
+
109
+ get lastY() {
110
+ const { yLabelRows } = this
111
+ return yLabelRows[yLabelRows.length - 1].value
112
+ }
113
+
114
+ get lineLabelHeight() {
115
+ return this.fontSize
116
+ }
117
+
118
+ get lineLabelBoxHeight() {
119
+ if (this.lineLabels.length > 0) {
120
+ return this.lineLabelMargin + this.lineLabelHeight
121
+ }
122
+ return 0
123
+ }
124
+
125
+ get xAxisStart() {
126
+ return this.xPadding + (this.xLabelWidth / 2)
127
+ }
128
+
129
+ get xAxisEnd() {
130
+ return this.xAxisStart + this.contentWidth
131
+ }
132
+
133
+ get xRatio() {
134
+ const lineWidth = this.xAxisEnd - this.xAxisStart
135
+ const xDelta = this.lastX - this.firstX
136
+ return xDelta / lineWidth
137
+ }
138
+
139
+ get yAxisStart() {
140
+ return this.height - this.yPadding - this.lineLabelBoxHeight -
141
+ this.xLabelHeight - this.xLabelMargin + (this.yLabelHeight / 2)
142
+ }
143
+
144
+ get yAxisEnd() {
145
+ return this.yAxisStart - this.contentHeight
146
+ }
147
+
148
+ get yRatio() {
149
+ const lineHeight = this.yAxisStart - this.yAxisEnd
150
+ const yDelta = Math.abs(this.lastY - this.firstY)
151
+ return yDelta / lineHeight
152
+ }
153
+
154
+ bindPointMouseOver() {
155
+ if (isUndef(this.options.onPointMouseOver)) {
156
+ return
157
+ }
158
+ if (! ('onmousemove' in this.canvas)) {
159
+ return
160
+ }
161
+ this.addLayer()
162
+ const canvas = this.getHighestCanvas()
163
+ this.addEvent(canvas, 'mousemove', throttle(this.handleMouseMove.bind(this), 30))
164
+ }
165
+
166
+ clearPointPos() {
167
+ this.pointPosMap.clear()
168
+ }
169
+
170
+ draw() {
171
+ this.clear()
172
+ this.drawXAxis()
173
+ this.drawYAxis()
174
+ this.drawBgLines()
175
+ this.drawLines()
176
+ this.drawLineLables()
177
+ }
178
+
179
+ drawBgLines() {
180
+
181
+ const { ctx, yLabelRows, contentWidth, firstY, xAxisStart, yAxisStart, yRatio } = this
182
+
183
+ ctx.strokeStyle = 'rgba(224, 224, 224, .5)'
184
+ ctx.lineWidth = 1
185
+
186
+ yLabelRows.forEach(row => {
187
+
188
+ const y = yAxisStart - ((row.value - firstY) / yRatio)
189
+
190
+ ctx.beginPath()
191
+ ctx.moveTo(xAxisStart, y)
192
+ ctx.lineTo(xAxisStart + contentWidth, y)
193
+ ctx.stroke()
194
+ ctx.closePath
195
+ })
196
+ }
197
+
198
+ drawLines() {
199
+ const { ctx, pointPosMap, lineStyles } = this
200
+ ctx.lineWidth = 2
201
+
202
+ this.pointsArr.forEach((points, i) => {
203
+ ctx.beginPath()
204
+ ctx.strokeStyle = lineStyles[i] ? lineStyles[i] : '#000'
205
+ points.forEach(p => {
206
+ const pos = pointPosMap.get(p)
207
+ ctx.lineTo(pos.x, pos.y)
208
+ })
209
+ ctx.stroke()
210
+ ctx.closePath()
211
+ })
212
+ }
213
+
214
+ drawLineLables() {
215
+ const { ctx, lineStyles, lineLabelHeight } = this
216
+ const rectSize = 7
217
+
218
+ const rectGutter = 7
219
+ const labelGutter = 14
220
+ const rectMargin = 1
221
+ const y = this.height - this.yPadding
222
+ let x = this.xPadding
223
+
224
+ this.lineLabels.forEach((name, i) => {
225
+ ctx.fillStyle = lineStyles[i] || '#000'
226
+ ctx.fillRect(x, y - lineLabelHeight + rectMargin, rectSize, rectSize)
227
+
228
+ x += (rectSize + rectGutter)
229
+ ctx.fillStyle = '#000'
230
+ ctx.textAlign = 'left'
231
+ ctx.textBaseline = 'top'
232
+ ctx.fillText(name, x, y - lineLabelHeight)
233
+ x += (ctx.measureText(name).width + labelGutter)
234
+ })
235
+ }
236
+
237
+ clearVerticalLine() {
238
+ const { ctx } = this.firstLayer
239
+ ctx.clearRect(0, 0, this.width, this.height)
240
+ }
241
+
242
+ drawVerticalLine(point, index) {
243
+ const { ctx } = this.firstLayer
244
+ const pos = this.pointPosMap.get(point)
245
+ const style = this.lineStyles[index] || '#000'
246
+ ctx.strokeStyle = style
247
+ ctx.lineWidth = 1
248
+ ctx.beginPath()
249
+ ctx.moveTo(pos.x, 0)
250
+ ctx.lineTo(pos.x, this.height)
251
+ ctx.stroke()
252
+ ctx.closePath()
253
+
254
+ this.fillCircle(ctx, pos.x, pos.y, 8, style, .2)
255
+ this.fillCircle(ctx, pos.x, pos.y, 4, style)
256
+ }
257
+
258
+ drawXAxis() {
259
+ const { ctx, firstX, xLabelRows, xAxisStart, xRatio } = this
260
+
261
+ const y = this.height - this.yPadding - this.lineLabelBoxHeight
262
+
263
+ const scaleMargin = 4
264
+ const scaleSize = 4
265
+ const scaleStart = y - scaleMargin - scaleSize
266
+ const scaleEnd = y - scaleMargin
267
+
268
+ ctx.textBaseline = 'top'
269
+ ctx.fillStyle = '#3c4257'
270
+ ctx.textAlign = 'center'
271
+
272
+ ctx.strokeStyle = '#3c4257'
273
+
274
+ xLabelRows.forEach((row, i) => {
275
+
276
+ const x = xAxisStart + ((row.value - firstX) / xRatio)
277
+
278
+ ctx.beginPath()
279
+ ctx.moveTo(x, scaleStart)
280
+ ctx.lineTo(x, scaleEnd)
281
+ ctx.stroke()
282
+ ctx.closePath()
283
+
284
+ ctx.fillText(row.label, x, y)
285
+ })
286
+ }
287
+
288
+ drawYAxis() {
289
+ const { ctx, firstY, yLabelRows, yAxisStart, yRatio } = this
290
+ const x = this.width - this.xPadding
291
+ const halfYLabelHeight = this.yLabelHeight / 2
292
+
293
+ ctx.fillStyle = '#3c4257'
294
+ ctx.textAlign = 'right'
295
+
296
+ yLabelRows.forEach(row => {
297
+ const y = yAxisStart - ((row.value - firstY) / yRatio)
298
+ ctx.fillText(row.label, x, y - halfYLabelHeight)
299
+ })
300
+ }
301
+
302
+ findClosetPoint(canvasMousePos) {
303
+ const { pointsArr, pointPosMap } = this
304
+ let index = 0
305
+ for (const points of pointsArr) {
306
+ for (const point of points) {
307
+ const pos = pointPosMap.get(point)
308
+ if (this.inDetectedZone(canvasMousePos, pos)) {
309
+ return {
310
+ index,
311
+ point
312
+ }
313
+ }
314
+ }
315
+ index++
316
+ }
317
+ }
318
+
319
+ handleDprChange() {
320
+ this.setDpr()
321
+ this.refresh()
322
+ }
323
+
324
+ handleMouseMove(event) {
325
+
326
+ const canvasMousePos = this.getMousePosInCanvas(event)
327
+ const res = this.findClosetPoint(canvasMousePos)
328
+
329
+ this.raf(() => {
330
+ this.clearVerticalLine()
331
+ if (res) {
332
+ this.drawVerticalLine(res.point, res.index)
333
+ }
334
+ // only fires if res differs
335
+ if (this.lastClosetPointRes !== res) {
336
+ const mousePos = this.getMousePos(canvasMousePos)
337
+ this.options.onPointMouseOver(mousePos, res)
338
+ }
339
+ this.lastClosetPointRes = res
340
+ })
341
+ }
342
+
343
+ inDetectedZone(canvasMousePos, pointPos) {
344
+ const zoneLength = 14
345
+ const { x: mouseX, y: mouseY } = canvasMousePos
346
+ const { x: pointX, y: pointY } = pointPos
347
+ /**
348
+ * # is canvasMousePos
349
+ * see if PointPos is landed in the zone below
350
+ *
351
+ * A -------- B
352
+ * | |
353
+ * | |
354
+ * #
355
+ * | |
356
+ * | |
357
+ * C -------- D
358
+ */
359
+ const a = {
360
+ x: mouseX - zoneLength,
361
+ y: mouseY - zoneLength
362
+ }
363
+ const b = {
364
+ x: mouseX + zoneLength,
365
+ y: mouseY - zoneLength
366
+ }
367
+ const c = {
368
+ x: mouseX - zoneLength,
369
+ y: mouseY + zoneLength
370
+ }
371
+ return (a.x <= pointX) && (pointX <= b.x) &&
372
+ (a.y <= pointY) && (pointY <= c.y)
373
+ }
374
+
375
+ getUniqSortedPoints(axis) {
376
+ const points = uniqBy(this.pointsArr.flat(), axis)
377
+ return sortBy(points, [axis])
378
+ }
379
+
380
+ getLabelRows(options = {}) {
381
+
382
+ const axis = options.axis || 'x'
383
+ const gutter = options.gutter || this.xGutter
384
+ const contentLength = options.contentLength || this.contentWidth
385
+ const toLabel = options.toLabel || this.toXLabel
386
+ const measureLength = options.measureLength || (v => this.ctx.measureText(v).width)
387
+
388
+ const points = this.getUniqSortedPoints(axis)
389
+ const firstPoint = points[0]
390
+ const lastPoint = points[points.length - 1]
391
+
392
+ if (points.length <= 2) {
393
+ return points.map(p => {
394
+ const value = p[axis]
395
+ const label = toLabel(value)
396
+ const length = measureLength(label)
397
+ return { label, length, value }
398
+ })
399
+ }
400
+
401
+ const firstPointValue = firstPoint[axis]
402
+ const lastPointValue = lastPoint[axis]
403
+ const pointLength = points.length
404
+
405
+ let step = options.step
406
+
407
+ if (isUndef(step)) {
408
+ step = this.getAutoStep(firstPointValue, lastPointValue, pointLength)
409
+ }
410
+
411
+ const [stepStart, stepEnd] = this.getStepStartEnd(step, firstPointValue, lastPointValue)
412
+ const values = range(stepStart, stepEnd + step, step)
413
+
414
+ const valueCount = values.length
415
+ const initialGap = parseInt((valueCount - 2) / 2, 10)
416
+
417
+ const firstValue = values[0]
418
+ const lastValue = values[valueCount - 1]
419
+ const firstLabel = toLabel(firstValue)
420
+ const lastLabel = toLabel(lastValue)
421
+
422
+ let stepRows = [
423
+ { label: firstLabel, length: measureLength(firstLabel), value: firstValue },
424
+ { label: lastLabel, length: measureLength(lastLabel), value: lastValue },
425
+ ]
426
+
427
+ for (let gap = initialGap; gap >= 0; gap--) {
428
+ const { lengthTotal, rows } = this.getLengthTotalData(gap, gutter, values, measureLength, toLabel)
429
+ if (lengthTotal <= contentLength) {
430
+ stepRows = rows
431
+ continue
432
+ }
433
+ return stepRows
434
+ }
435
+ return stepRows
436
+ }
437
+
438
+ refresh() {
439
+ this.raf(() => {
440
+ this.clearPointPos()
441
+ this.setDomWidthIfNeeded()
442
+ this.setCanvasSize(this.canvas)
443
+ this.layers.forEach(layer => this.setCanvasSize(layer.canvas))
444
+ this.setLabelWidths()
445
+ this.setLabelHeights()
446
+ this.setAxisData()
447
+ this.setPointPos()
448
+ this.draw()
449
+ })
450
+ }
451
+
452
+ setLabelHeights() {
453
+ this.xLabelHeight = this.fontSize
454
+ this.yLabelHeight = this.fontSize
455
+ }
456
+
457
+ setLabelWidths() {
458
+
459
+ const { toXLabel, toYLabel, ctx } = this
460
+ const { xLabelWidth, yLabelWidth } = this.pointsArr.flat()
461
+ .filter(p => p)
462
+ .reduce((o, p) => {
463
+
464
+ const { xLabelWidth, yLabelWidth } = o
465
+ const measuredXLabelWidth = ctx.measureText(toXLabel(p.x)).width
466
+ const measuredYLabelWidth = ctx.measureText(toYLabel(p.y)).width
467
+
468
+ return {
469
+ xLabelWidth: (measuredXLabelWidth > xLabelWidth) ? measuredXLabelWidth : xLabelWidth,
470
+ yLabelWidth: (measuredYLabelWidth > yLabelWidth) ? measuredYLabelWidth : yLabelWidth,
471
+ }
472
+ }, {
473
+ xLabelWidth: 0,
474
+ yLabelWidth: 0
475
+ })
476
+
477
+ this.xLabelWidth = xLabelWidth
478
+ this.yLabelWidth = yLabelWidth
479
+ }
480
+
481
+ setAxisData() {
482
+ this.xLabelRows = this.getLabelRows({ step: this.xStep })
483
+ this.yLabelRows = this.getLabelRows({
484
+ axis: 'y',
485
+ step: this.yStep,
486
+ gutter: this.yGutter,
487
+ contentLength: this.contentHeight,
488
+ toLabel: this.toYLabel,
489
+ measureLength: () => this.yLabelHeight
490
+ })
491
+ }
492
+
493
+ setData(pointsArr) {
494
+ this.pointsArr = pointsArr
495
+ this.clearPointPos()
496
+ this.setLabelWidths()
497
+ this.setLabelHeights()
498
+ this.setAxisData()
499
+ this.setPointPos()
500
+ this.raf(() => this.draw())
501
+ }
502
+
503
+ setPointPos() {
504
+ const { firstX, firstY, pointPosMap, xAxisStart, xRatio, yAxisStart, yRatio } = this
505
+
506
+ this.pointsArr.forEach((points, i) => {
507
+ points.forEach(point => {
508
+ const x = xAxisStart + ((point.x - firstX) / xRatio)
509
+ const y = yAxisStart - ((point.y - firstY) / yRatio)
510
+ const pos = { x, y }
511
+ pointPosMap.set(point, pos)
512
+ })
513
+ })
514
+ }
515
+
516
+ destroy() {
517
+ const { dom, canvas } = this
518
+ const { toXLabel, toYLabel } = this.options
519
+
520
+ if (isDef(toXLabel)) {
521
+ mem.clear(this.toXLabel)
522
+ }
523
+ if (isDef(toYLabel)) {
524
+ mem.clear(this.toYLabel)
525
+ }
526
+ this.clearPointPos()
527
+ this.unbindMedia()
528
+ this.removeAllLayers()
529
+
530
+ if (dom.contains(canvas)) {
531
+ dom.removeChild(canvas)
532
+ dom.style.removeProperty('position')
533
+ }
534
+ }
535
+ }