beyond-rails 0.0.240 → 0.0.246

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27fa06609359c2b1ee1113771ce48fa3c2693f925e1b3f909e85d18c08766f08
4
- data.tar.gz: 9ac7de120419f1aa97f874876694fe9ace1ec2239657199afff0d32980c835d3
3
+ metadata.gz: ebefc15d0cab8d8554a18f030ae3041af23f3aab56633bd12aa9e80f823454d9
4
+ data.tar.gz: a54e416999fce854ecdcc3194d3e7226475f56f8d3ae63d59d638a1651eb8751
5
5
  SHA512:
6
- metadata.gz: 1a54840c057c320dce8f19ebefb6388877e20bcebdf728a50cc5b8193822cb2a948e55423ed3802d20f1fa6628486947c79c847ea63a5637462b04a1400633ef
7
- data.tar.gz: 399fd279bfc53c652babd56f10e481ce33bc994df4391588f26729825b971923ce51186ffe64e48238b3135e834b58131e0fd742ab1bad49107a0397f470339a
6
+ metadata.gz: acb1c4dba756a4d2fa6a0ca378c02e52d2ffb7703bc5d13d1ff298e9604e76250c7124066918e7b45809355f358d100f2b8a293be81292ec00579d93a91433f9
7
+ data.tar.gz: 78dc63e30de5eb027bba31a7cbdefe1c9737774f3090dc69a3f2e243a3d2ffd9a2bb17a33248e6f1d5c5928b5558f50629d460fac15e5eef96d2a4bfb8b66604
@@ -4,12 +4,7 @@ import isDef from '../utils/isDef'
4
4
  import isUndef from '../utils/isUndef'
5
5
  import isInt from '../utils/isInt'
6
6
  import { mem, range, sortBy, throttle, uniqBy } from '../utils'
7
-
8
- const defaultBarStyles = [
9
- '#5469d4',
10
- '#7c54d4',
11
- '#a254d4'
12
- ]
7
+ import { DEFAULT_CHART_STYLES } from '../consts'
13
8
 
14
9
  @supportDom
15
10
  @chartCommon
@@ -35,8 +30,8 @@ export default class BarChart {
35
30
  this.yLabelMargin = isDef(options.yLanelMargin) ? options.yLabelMargin : 14
36
31
 
37
32
  this.fontSize = options.fontSize || 12
38
- this.bgColor = options.bgColor || '#fff'
39
- this.barStyles = options.barStyles || defaultBarStyles
33
+ this.bg = options.bg || '#fff'
34
+ this.barStyles = options.barStyles || DEFAULT_CHART_STYLES
40
35
 
41
36
  this.yLabelRows = []
42
37
  this.barPosMap = new Map()
@@ -3,6 +3,7 @@ import chartCommon from '../decorators/chartCommon'
3
3
  import isDef from '../utils/isDef'
4
4
  import isUndef from '../utils/isUndef'
5
5
  import { mem, range, sortBy, throttle, uniqBy } from '../utils'
6
+ import { DEFAULT_CHART_STYLES } from '../consts'
6
7
 
7
8
  /**
8
9
  * -------------------------------------------------------------------------------------------------
@@ -27,11 +28,6 @@ import { mem, range, sortBy, throttle, uniqBy } from '../utils'
27
28
  * v |
28
29
  * --------------------------------------------------------------------------------------------------
29
30
  **/
30
- const defaultLineStyles = [
31
- '#5469d4',
32
- '#7c54d4',
33
- '#a254d4'
34
- ]
35
31
 
36
32
  @supportDom
37
33
  @chartCommon
@@ -56,16 +52,15 @@ export default class LineChart {
56
52
  this.xLabelMargin = isDef(options.xLabelMargin) ? options.xLabelMargin : 10
57
53
  this.yLabelMargin = isDef(options.yLanelMargin) ? options.yLabelMargin : 10
58
54
 
59
- this.lineStyles = options.lineStyles || defaultLineStyles
55
+ this.lineStyles = options.lineStyles || DEFAULT_CHART_STYLES
60
56
 
61
- this.bgColor = options.bgColor || '#fff'
57
+ this.bg = options.bg || '#fff'
62
58
  this.fontSize = options.fontSize || 12
63
59
 
64
60
  this.xStep = options.xStep
65
61
  this.yStep = options.yStep
66
62
 
67
63
  this.lineLabels = options.lineLabels || []
68
- this.lineLabelMargin = isDef(options.lineLabelMargin) ? options.lineLabelMargin : 20
69
64
 
70
65
  this.pointPosMap = new Map()
71
66
  this.xLabelRows = []
@@ -78,6 +73,7 @@ export default class LineChart {
78
73
  this.setDpr()
79
74
  this.setDomSizeIfNeeded()
80
75
  this.setCanvas()
76
+ this.setLabelBox()
81
77
  this.clear()
82
78
  this.bindMedia()
83
79
  this.bindPointMouseOver()
@@ -93,8 +89,7 @@ export default class LineChart {
93
89
  }
94
90
 
95
91
  get contentHeight() {
96
- return this.height - (this.yPadding * 2) - this.xLabelMargin -
97
- this.xLabelHeight - this.lineLabelBoxHeight
92
+ return this.height - (this.yPadding * 2) - this.xLabelMargin - this.xLabelHeight
98
93
  }
99
94
 
100
95
  get firstX() {
@@ -115,17 +110,6 @@ export default class LineChart {
115
110
  return yLabelRows[yLabelRows.length - 1].value
116
111
  }
117
112
 
118
- get lineLabelHeight() {
119
- return this.fontSize
120
- }
121
-
122
- get lineLabelBoxHeight() {
123
- if (this.lineLabels.length > 0) {
124
- return this.lineLabelMargin + this.lineLabelHeight
125
- }
126
- return 0
127
- }
128
-
129
113
  get xAxisStart() {
130
114
  return this.xPadding + (this.xLabelWidth / 2)
131
115
  }
@@ -145,7 +129,7 @@ export default class LineChart {
145
129
  }
146
130
 
147
131
  get yAxisStart() {
148
- return this.height - this.yPadding - this.lineLabelBoxHeight -
132
+ return this.height - this.yPadding -
149
133
  this.xLabelHeight - this.xLabelMargin + (this.yLabelHeight / 2)
150
134
  }
151
135
 
@@ -185,7 +169,6 @@ export default class LineChart {
185
169
  this.drawYAxis()
186
170
  this.drawBgLines()
187
171
  this.drawLines()
188
- this.drawLineLables()
189
172
  }
190
173
 
191
174
  drawBgLines() {
@@ -232,29 +215,6 @@ export default class LineChart {
232
215
  })
233
216
  }
234
217
 
235
- drawLineLables() {
236
- const { ctx, lineStyles, lineLabelHeight } = this
237
- const rectSize = 7
238
-
239
- const rectGutter = 7
240
- const labelGutter = 14
241
- const rectMargin = 1
242
- const y = this.height - this.yPadding
243
- let x = this.xPadding
244
-
245
- this.lineLabels.forEach((name, i) => {
246
- ctx.fillStyle = lineStyles[i] || '#000'
247
- ctx.fillRect(x, y - lineLabelHeight + rectMargin, rectSize, rectSize)
248
-
249
- x += (rectSize + rectGutter)
250
- ctx.fillStyle = '#000'
251
- ctx.textAlign = 'left'
252
- ctx.textBaseline = 'top'
253
- ctx.fillText(name, x, y - lineLabelHeight)
254
- x += (ctx.measureText(name).width + labelGutter)
255
- })
256
- }
257
-
258
218
  clearVerticalLine() {
259
219
  const { ctx } = this.firstLayer
260
220
  ctx.clearRect(0, 0, this.width, this.height)
@@ -279,7 +239,7 @@ export default class LineChart {
279
239
  drawXAxis() {
280
240
  const { ctx, firstX, xLabelRows, xAxisStart, xRatio } = this
281
241
 
282
- const y = this.height - this.yPadding - this.lineLabelBoxHeight
242
+ const y = this.height - this.yPadding
283
243
 
284
244
  const scaleMargin = 4
285
245
  const scaleSize = 4
@@ -538,7 +498,10 @@ export default class LineChart {
538
498
  return this.raf(() => this.clear())
539
499
  }
540
500
  this.setPointPos()
541
- this.raf(() => this.draw())
501
+ this.raf(() => {
502
+ this.drawLabels(this.lineLabels, this.lineStyles)
503
+ this.draw()
504
+ })
542
505
  }
543
506
 
544
507
  setPointPos() {
@@ -0,0 +1,75 @@
1
+ import { DEFAULT_TIMEZONE } from '../consts'
2
+ import supportDom from '../decorators/supportDom'
3
+ import isTouchDevice from '../utils/isTouchDevice'
4
+ import { format } from '../utils'
5
+
6
+ @supportDom
7
+ export default class MonthInput {
8
+
9
+ constructor(dom, date, options = {}) {
10
+ this.active = false
11
+ this.danger = false
12
+ this.dom = dom
13
+ this.date = date
14
+ this.options = options
15
+ this.tz = options.tz || DEFAULT_TIMEZONE
16
+ this.datePattern = options.datePattern || 'yyyy/MM'
17
+ this.init()
18
+ }
19
+
20
+ focus() {
21
+ this.dom.focus()
22
+ }
23
+
24
+ init() {
25
+ this.initInput()
26
+ this.addEvents()
27
+ }
28
+
29
+ initInput() {
30
+ const { dom } = this
31
+ if (this.date) {
32
+ dom.value = this.format(this.date)
33
+ }
34
+ if (! dom.hasAttribute('placeholder')) {
35
+ dom.setAttribute('placeholder', this.datePattern.toUpperCase())
36
+ }
37
+ if (isTouchDevice()) {
38
+ dom.setAttribute('readonly', 'readonly')
39
+ }
40
+ }
41
+
42
+ format(date) {
43
+ return format(date, this.datePattern, { timezone: this.tz })
44
+ }
45
+
46
+ setDate(date) {
47
+ this.date = date
48
+ this.dom.value = date ? this.format(date) : ''
49
+ }
50
+
51
+ setActive(active) {
52
+ this.active = active
53
+ const func = active ? 'add' : 'remove'
54
+ this.dom.classList[func]('active')
55
+ }
56
+
57
+ setDanger(danger) {
58
+ this.danger = danger
59
+ const func = danger ? 'add' : 'remove'
60
+ this.dom.classList[func]('danger')
61
+ }
62
+
63
+ clearStatus() {
64
+ this.setActive(false)
65
+ this.setDanger(false)
66
+ }
67
+
68
+ addEvents() {
69
+ const { dom } = this
70
+ this.addEvent(dom, 'click', event => this.fire('click', event))
71
+ this.addEvent(dom, 'focus', event => this.fire('focus', event))
72
+ this.addEvent(dom, 'keyup', event => this.fire('keyup', event))
73
+ this.addEvent(dom, 'blur', event => this.fire('blur', event))
74
+ }
75
+ }
@@ -0,0 +1,262 @@
1
+ import supportDom from '../decorators/supportDom'
2
+ import getFloatedTargetPos from '../utils/getFloatedTargetPos'
3
+ import isTouchDevice from '../utils/isTouchDevice'
4
+ import { DEFAULT_TIMEZONE, DEFAULT_LOCALE } from '../consts'
5
+ import {
6
+ chunk,
7
+ format,
8
+ range,
9
+ setYear,
10
+ setMonth,
11
+ getYear,
12
+ getMonth,
13
+ addYears,
14
+ toPixel,
15
+ subYears,
16
+ noop
17
+ } from '../utils'
18
+
19
+ @supportDom
20
+ export default class MonthMenu {
21
+
22
+ constructor(options = {}) {
23
+ const { dom, date } = options
24
+ this.container = dom
25
+ this.date = date
26
+ this.menuDate = this.date || new Date()
27
+ this.options = options
28
+ this.tz = options.tz || DEFAULT_TIMEZONE
29
+ this.locale = options.locale || DEFAULT_LOCALE
30
+ this.change = options.change || noop
31
+ this.isVisible = false
32
+ this.loopIndex = 0
33
+ this.init()
34
+ }
35
+
36
+ init() {
37
+ this.addMenu()
38
+ this.addEvents()
39
+ }
40
+
41
+ renderTableContent() {
42
+ const { date, menuDate, locale } = this
43
+
44
+ const currentYear = date ? getYear(date) : null
45
+ const currentMonth = date ? getMonth(date) : null
46
+
47
+ return chunk(range(0, 12), 3)
48
+ .map(months => {
49
+ const tds = months.map(month => {
50
+ const d = setMonth(menuDate, month)
51
+ const text = format(d, 'MMM', { locale })
52
+
53
+ const isCurrentMonth = (currentYear === getYear(d)) && (currentMonth === getMonth(d))
54
+
55
+ const classname = isCurrentMonth ? 'cell selected-ex' : 'cell'
56
+ return `<td class="${classname}" data-month-td="${month}">${text}</td>`
57
+ }).join('')
58
+ return `<tr>${tds}</tr>`
59
+ })
60
+ .join('')
61
+ }
62
+
63
+ updateTableContent() {
64
+ this.table.innerHTML = this.renderTableContent()
65
+ }
66
+
67
+ addMenu() {
68
+ const { container } = this
69
+ const dom = document.createElement('div')
70
+ dom.classList.add('month-menu')
71
+
72
+ if (container) {
73
+ dom.classList.add('static')
74
+ }
75
+ const title = getYear(this.menuDate)
76
+ dom.innerHTML = `
77
+ <div class="month-menu-content">
78
+ <div class="month-menu-caption">
79
+ <button class="month-menu-caption-btn"
80
+ type="button"
81
+ data-prev-month-btn>
82
+ <i class="icon-chevron-left"></i>
83
+ </button>
84
+ <div data-month-menu-title>${title}</div>
85
+ <button class="month-menu-caption-btn"
86
+ type="button"
87
+ data-next-month-btn>
88
+ <i class="icon-chevron-right"></i>
89
+ </button>
90
+ </div>
91
+ <table class="month-menu-table"
92
+ data-month-table>
93
+ ${this.renderTableContent()}
94
+ </table>
95
+ </div>
96
+ `
97
+ if (container) {
98
+ container.appendChild(dom)
99
+ }
100
+ else {
101
+ document.body.appendChild(dom)
102
+ }
103
+ this.menuTitle = dom.querySelector('[data-month-menu-title]')
104
+ this.prevBtn = dom.querySelector('[data-prev-month-btn]')
105
+ this.nextBtn = dom.querySelector('[data-next-month-btn]')
106
+ this.table = dom.querySelector('[data-month-table]')
107
+ this.dom = dom
108
+ }
109
+
110
+ setTitle(date) {
111
+ this.menuTitle.textContent = getYear(date)
112
+ }
113
+
114
+ addYear(year = 1) {
115
+ this.menuDate = addYears(this.menuDate, year)
116
+ this.setTitle(this.menuDate)
117
+ this.updateTableContent()
118
+ }
119
+
120
+ addYearLoop() {
121
+ const duration = (this.loopIndex === 0) ? 500 : 100
122
+ this.addYearTimer = setTimeout(() => {
123
+ this.loopIndex += 1
124
+ const year = parseInt((this.loopIndex / 5) + 1, 10)
125
+ this.addYear(year)
126
+ this.addYearLoop()
127
+ }, duration)
128
+ }
129
+
130
+ clearAddYearLoop() {
131
+ clearTimeout(this.addYearTimer)
132
+ this.loopIndex = 0
133
+ }
134
+
135
+ subYear(year = 1) {
136
+ this.menuDate = subYears(this.menuDate, year)
137
+ this.setTitle(this.menuDate)
138
+ this.updateTableContent()
139
+ }
140
+
141
+ subYearLoop() {
142
+ const duration = (this.loopIndex === 0) ? 500 : 100
143
+ this.subYearTimer = setTimeout(() => {
144
+ this.loopIndex += 1
145
+ const year = parseInt((this.loopIndex / 5) + 1, 10)
146
+ const currentYear = getYear(this.menuDate)
147
+ if ((currentYear - year) > 0) {
148
+ this.subYear(year)
149
+ this.subYearLoop()
150
+ }
151
+ }, duration)
152
+ }
153
+
154
+ clearSubYearLoop() {
155
+ clearTimeout(this.subYearTimer)
156
+ this.loopIndex = 0
157
+ }
158
+
159
+ stop(event) {
160
+ event.preventDefault()
161
+ event.stopPropagation()
162
+ }
163
+
164
+ addEvents() {
165
+ const isTouch = isTouchDevice()
166
+ const downEvent = isTouch ? 'touchstart' : 'mousedown'
167
+
168
+ this.addEvent(this.prevBtn, downEvent, event => {
169
+ this.stop(event)
170
+ const currentYear = getYear(this.menuDate)
171
+ if ((currentYear - 1) <= 0) {
172
+ return
173
+ }
174
+ this.subYear()
175
+ this.subYearLoop()
176
+ })
177
+
178
+ this.addEvent(this.nextBtn, 'click', event => event.stopPropagation())
179
+ this.addEvent(this.prevBtn, 'click', event => event.stopPropagation())
180
+
181
+ this.addEvent(this.nextBtn, downEvent, event => {
182
+ this.stop(event)
183
+ this.addYear()
184
+ this.addYearLoop()
185
+ })
186
+
187
+ const upEvent = isTouch ? 'touchend' : 'mouseup'
188
+ this.addEvent(this.prevBtn, upEvent, event => {
189
+ this.stop(event)
190
+ this.clearSubYearLoop()
191
+ })
192
+ this.addEvent(this.nextBtn, upEvent, event => {
193
+ this.stop(event)
194
+ this.clearAddYearLoop()
195
+ })
196
+
197
+ this.addEvent(this.table, 'click', event => {
198
+ this.stop(event)
199
+ const { target } = event
200
+ if ('monthTd' in target.dataset) {
201
+ const year = getYear(this.menuDate)
202
+ const month = parseInt(target.dataset.monthTd, 10)
203
+
204
+ if (! this.date) {
205
+ this.date = new Date(this.menuDate.getTime())
206
+ }
207
+
208
+ this.date = setYear(this.date, year)
209
+ this.date = setMonth(this.date, month)
210
+ this.updateTableContent()
211
+ this.emitChange()
212
+ }
213
+ })
214
+ }
215
+
216
+ setDate(date) {
217
+ this.date = date
218
+ this.menuDate = date
219
+ this.setTitle(date)
220
+ this.updateTableContent()
221
+ }
222
+
223
+ emitChange() {
224
+ this.change(this.date)
225
+ }
226
+
227
+ pos(src) {
228
+ const { dom } = this
229
+ const { pos } = getFloatedTargetPos({
230
+ src,
231
+ target: dom,
232
+ place: 'bottom',
233
+ align: 'left',
234
+ offset: 4
235
+ })
236
+ dom.style.left = toPixel(pos.left)
237
+ dom.style.top = toPixel(pos.top)
238
+ }
239
+
240
+ show(src) {
241
+ const { dom } = this
242
+ dom.style.display = 'block'
243
+ if (src) {
244
+ this.pos(src)
245
+ }
246
+ dom.style.opacity = 1
247
+ this.isVisible = true
248
+ }
249
+
250
+ hide() {
251
+ this.dom.style.display = 'none'
252
+ this.isVisible = false
253
+ }
254
+
255
+ destroy() {
256
+ this.menuTitle = null
257
+ this.prevBtn = null
258
+ this.nextBtn = null
259
+ this.table = null
260
+ this.dom.remove()
261
+ }
262
+ }
@@ -0,0 +1,90 @@
1
+ import MonthInput from './MonthInput'
2
+ import MonthMenu from './MonthMenu'
3
+ import supportDom from '../decorators/supportDom'
4
+ import { DEFAULT_TIMEZONE } from '../consts'
5
+ import { noop, parse } from '../utils'
6
+
7
+ @supportDom
8
+ export default class Monthpicker {
9
+
10
+ constructor(dom, options = {}) {
11
+ this.dom = dom
12
+ this.tz = options.tz || DEFAULT_TIMEZONE
13
+ this.date = options.date
14
+ this.menuDate = this.date
15
+
16
+ this.options = options
17
+ this.backdropMode = options.backdropMode || 'auto'
18
+ this.change = options.change || noop
19
+ this.init()
20
+ }
21
+
22
+ init() {
23
+ const { dom } = this
24
+ this.monthInput = new MonthInput(
25
+ dom.querySelector('[data-month]'),
26
+ this.date,
27
+ this.options
28
+ )
29
+ this.monthMenu = new MonthMenu({
30
+ date: this.menuDate,
31
+ change: date => {
32
+ this.monthInput.setDate(date)
33
+ this.monthInput.clearStatus()
34
+ this.monthMenu.hide()
35
+ }
36
+ })
37
+ this.addEvents()
38
+ }
39
+
40
+ handleMonthInputFocus() {
41
+ const { monthInput } = this
42
+ monthInput.clearStatus()
43
+ monthInput.setActive(true)
44
+ this.monthMenu.show(this.dom)
45
+ }
46
+
47
+ handleMonthInputKeyUp(event) {
48
+ const { monthInput } = this
49
+ const { value } = event.target
50
+ const res = parse(value, monthInput.datePattern, this.date)
51
+
52
+ if (res.toString() === 'Invalid Date') {
53
+ monthInput.setDanger(true)
54
+ this.nextDate = null
55
+ }
56
+ else {
57
+ monthInput.setDanger(false)
58
+ this.nextDate = res
59
+ }
60
+ }
61
+
62
+ handleMonthInputBlur() {
63
+ if (this.nextDate) {
64
+ this.date = this.nextDate
65
+ this.menuDate = this.date
66
+ this.monthMenu.setDate(this.menuDate)
67
+ this.nextDate = null
68
+ }
69
+ }
70
+
71
+ addEvents() {
72
+ const { monthInput } = this
73
+ monthInput.on('focus', () => this.handleMonthInputFocus())
74
+ monthInput.on('click', event => event.stopPropagation())
75
+ monthInput.on('keyup', event => this.handleMonthInputKeyUp(event))
76
+ monthInput.on('blur', () => this.handleMonthInputBlur())
77
+
78
+ if (this.backdropMode === 'auto') {
79
+ this.addEvent(document, 'click', () => {
80
+ this.monthMenu.hide()
81
+ this.monthInput.clearStatus()
82
+ })
83
+ }
84
+ }
85
+
86
+ destroy() {
87
+ this.monthInput.destroy()
88
+ this.monthMenu.destroy()
89
+ }
90
+ }
@@ -0,0 +1,256 @@
1
+ import supportDom from '../decorators/supportDom'
2
+ import chartCommon from '../decorators/chartCommon'
3
+ import isFn from '../utils/isFn'
4
+ import isDef from '../utils/isDef'
5
+ import isUndef from '../utils/isUndef'
6
+ import { throttle } from '../utils'
7
+ import { DEFAULT_CHART_STYLES } from '../consts'
8
+
9
+ @supportDom
10
+ @chartCommon
11
+ export default class PieChart {
12
+
13
+ constructor(dom, options = {}) {
14
+ this.dom = dom
15
+ this.data = []
16
+ this.total = 0
17
+
18
+ this.options = options
19
+ this.height = options.height
20
+ this.width = options.width
21
+ this.padding = isDef(options.padding) ? options.padding : 30
22
+ this.styles = options.styles || DEFAULT_CHART_STYLES
23
+ this.bg = options.bg || '#fff'
24
+
25
+ this.init()
26
+ }
27
+
28
+ init() {
29
+ this.setDpr()
30
+ this.setDomSizeIfNeeded()
31
+ this.setCanvas()
32
+ this.setLabelBox()
33
+ this.clear()
34
+ this.bindMedia()
35
+ this.bindPointMouseOver()
36
+ }
37
+
38
+ get x() {
39
+ return this.width / 2
40
+ }
41
+
42
+ get y() {
43
+ return this.height / 2
44
+ }
45
+
46
+ get radius() {
47
+ return this.contentWidth / 2
48
+ }
49
+
50
+ get pieWidth() {
51
+ return this.radius * .3
52
+ }
53
+
54
+ get centerCircleRadius() {
55
+ return this.radius - this.pieWidth
56
+ }
57
+
58
+ get contentWidth() {
59
+ return this.width - (this.padding * 2)
60
+ }
61
+
62
+ get contentHeight() {
63
+ return this.height - (this.padding * 2)
64
+ }
65
+
66
+ bindPointMouseOver() {
67
+ if (isUndef(this.options.onPieMouseOver)) {
68
+ return
69
+ }
70
+ if (! ('onmousemove' in this.canvas)) {
71
+ return
72
+ }
73
+ this.addLayer()
74
+ const canvas = this.getHighestCanvas()
75
+ this.addEvent(canvas, 'mousemove', throttle(this.handleMouseMove.bind(this), 30))
76
+ }
77
+
78
+ draw() {
79
+ this.clear()
80
+ this.drawPie()
81
+ }
82
+
83
+ drawPie() {
84
+ const { x, y, radius, centerCircleRadius, ctx, total } = this
85
+
86
+ let distance = 0
87
+
88
+ this.data.forEach((row, i) => {
89
+
90
+ const ratio = (row.value / total)
91
+ const startAngle = Math.PI * (-.5 + 2 * distance)
92
+ const endAngle = Math.PI * (-.5 + 2 * (distance + ratio))
93
+
94
+ const options = {
95
+ style: this.styles[i]
96
+ }
97
+ this.fillArc(ctx, x, y, radius, startAngle, endAngle, options)
98
+ distance += ratio
99
+ })
100
+
101
+ this.fillCircle(ctx, x, y, centerCircleRadius, '#fff')
102
+ }
103
+
104
+ handleDprChange() {
105
+ this.setDpr()
106
+ this.refresh()
107
+ }
108
+
109
+ getPosAngle(x1, y1, x2, y2) {
110
+
111
+ let x = x2
112
+ let y = y2
113
+
114
+ if (x1 >= 0) {
115
+ x -= x1
116
+ }
117
+ if (y1 >= 0) {
118
+ y -= y1
119
+ }
120
+ if (x1 < 0) {
121
+ x += x1
122
+ }
123
+ if (y2 < 0) {
124
+ y += y2
125
+ }
126
+
127
+ let angle = Math.atan2(y, x) * 180 / Math.PI
128
+ if (angle < 0) {
129
+ angle = 180 + (180 + angle)
130
+ }
131
+ return (angle + 90) % 360
132
+ }
133
+
134
+ handleMouseMove(event) {
135
+
136
+ const { x, y } = this
137
+ const canvasMousePos = this.getMousePosInCanvas(event)
138
+ const mousePos = this.getMousePos(canvasMousePos)
139
+ const mouseX = canvasMousePos.x
140
+ const mouseY = canvasMousePos.y
141
+
142
+ const distanceToCenterPoint = Math.sqrt(Math.pow(mouseX - x, 2) +
143
+ Math.pow(mouseY - y, 2))
144
+
145
+ const inCenterCircle = distanceToCenterPoint <= this.centerCircleRadius
146
+
147
+ this.clearSliceGlow()
148
+
149
+ if (inCenterCircle) {
150
+ return this.options.onPieMouseOver(mousePos, null)
151
+ }
152
+
153
+ const inPieCircle = distanceToCenterPoint <= this.radius
154
+ if (! inPieCircle) {
155
+ return this.options.onPieMouseOver(mousePos, null)
156
+ }
157
+ const angle = this.getPosAngle(x, y, mouseX, mouseY)
158
+ const matchedRow = this.data.find(row => {
159
+ return (row.startAngle <= angle) && (angle <= row.endAngle)
160
+ })
161
+ if (matchedRow) {
162
+ this.drawSliceGlow(matchedRow)
163
+ this.options.onPieMouseOver(mousePos, matchedRow)
164
+ }
165
+ }
166
+
167
+ drawSliceGlow(row) {
168
+ const index = this.data.findIndex(r => r === row)
169
+ this.clearSliceGlow()
170
+ const { x, y, radius, centerCircleRadius } = this
171
+ const ctx = this.firstLayer.canvas.getContext('2d')
172
+
173
+ const delta = 90 * Math.PI / 180
174
+ const startAngle = (row.startAngle * Math.PI / 180) - delta
175
+ const endAngle = (row.endAngle * Math.PI / 180) - delta
176
+
177
+ const options = {
178
+ style: this.styles[index],
179
+ alpha: .3
180
+ }
181
+ const radiusDelta = (radius - centerCircleRadius) * .3
182
+ this.fillArc(ctx, x, y, radius + radiusDelta, startAngle, endAngle, options)
183
+ this.fillCircle(this.firstLayer.ctx, x, y, centerCircleRadius, '#fff')
184
+ }
185
+
186
+ clearSliceGlow() {
187
+ const ctx = this.firstLayer.canvas.getContext('2d')
188
+ ctx.clearRect(0, 0, this.width, this.height)
189
+ }
190
+
191
+ refresh() {
192
+ this.raf(() => {
193
+ this.clearCanvasSize(this.canvas)
194
+ this.layers.forEach(layer => this.clearCanvasSize(layer.canvas))
195
+ this.setDomSizeIfNeeded()
196
+ this.setCanvasSize(this.canvas)
197
+ this.layers.forEach(layer => this.setCanvasSize(layer.canvas))
198
+ this.draw()
199
+ })
200
+ }
201
+
202
+ setAngles(data) {
203
+ const { total } = this
204
+ let startAngle = 0
205
+ return data.map(row => {
206
+ const endAngle = startAngle + ((row.value / total) * 360)
207
+ const nextRow = Object.assign({}, row, { startAngle, endAngle })
208
+ startAngle = endAngle
209
+ return nextRow
210
+ })
211
+ }
212
+
213
+ handleLabelMouseOver(event, index) {
214
+ const row = this.data[index]
215
+ this.drawSliceGlow(row)
216
+
217
+ if (isFn(this.options.onLabelMouseOver)) {
218
+ const canvasMousePos = this.getMousePosInCanvas(event)
219
+ const mousePos = this.getMousePos(canvasMousePos)
220
+ this.options.onLabelMouseOver(mousePos, row)
221
+ }
222
+ }
223
+
224
+ handleLabelMouseLeave(event, index) {
225
+ this.clearSliceGlow()
226
+
227
+ if (isFn(this.options.onLabelMouseOver)) {
228
+ const canvasMousePos = this.getMousePosInCanvas(event)
229
+ const mousePos = this.getMousePos(canvasMousePos)
230
+ this.options.onLabelMouseOver(mousePos)
231
+ }
232
+ }
233
+
234
+ setData(arr) {
235
+ const data = arr || []
236
+ this.total = data.reduce((t, row) => t + row.value, 0)
237
+ this.data = this.setAngles(data)
238
+ this.raf(() => {
239
+ const labels = this.data.map(row => row.label)
240
+ this.drawLabels(labels, this.styles)
241
+ this.draw()
242
+ })
243
+ }
244
+
245
+ destroy() {
246
+ const { dom, canvas } = this
247
+
248
+ this.unbindMedia()
249
+ this.removeAllLayers()
250
+
251
+ if (dom.contains(canvas)) {
252
+ dom.removeChild(canvas)
253
+ dom.style.removeProperty('position')
254
+ }
255
+ }
256
+ }
@@ -3,3 +3,13 @@ import locale from 'date-fns/locale/zh-TW'
3
3
  export const DEFAULT_TIMEZONE = 'Asia/Taipei'
4
4
 
5
5
  export const DEFAULT_LOCALE = locale
6
+
7
+ export const DEFAULT_CHART_STYLES = [
8
+ '#5469d4',
9
+ '#7c54d4',
10
+ '#a254d4',
11
+ '#c040a2',
12
+ '#ff5604',
13
+ '#0be4e3',
14
+ '#00d924'
15
+ ]
@@ -1,6 +1,8 @@
1
1
  import raf from '../utils/raf'
2
2
  import isUndef from '../utils/isUndef'
3
+ import isFn from '../utils/isFn'
3
4
  import { getDomPos, range, toPixel, isFunction } from '../utils'
5
+ import { DEFAULT_CHART_STYLES } from '../consts'
4
6
 
5
7
  export default function chartCommon(target) {
6
8
 
@@ -12,6 +14,7 @@ export default function chartCommon(target) {
12
14
  }
13
15
 
14
16
  init() {
17
+ this.offLabels = []
15
18
  this.layers = []
16
19
  if (isFunction(super.init)) {
17
20
  super.init()
@@ -50,10 +53,15 @@ export default function chartCommon(target) {
50
53
 
51
54
  clear() {
52
55
  const { ctx } = this
53
- ctx.fillStyle = this.bgColor
56
+ ctx.fillStyle = this.bg
54
57
  ctx.fillRect(0, 0, this.width, this.height)
55
58
  }
56
59
 
60
+ getHypotenuse(x1, y1, x2, y2) {
61
+ return Math.sqrt(Math.pow(x2 - x1, 2) +
62
+ Math.pow(y2 - y1, 2))
63
+ }
64
+
57
65
  fillCircle(ctx, x, y, radius, style, alpha) {
58
66
  ctx.save()
59
67
  ctx.beginPath()
@@ -65,6 +73,18 @@ export default function chartCommon(target) {
65
73
  ctx.restore()
66
74
  }
67
75
 
76
+ fillArc(ctx, x, y, radius, startAngle = 0, endAngle = 2 * Math.PI, options = {}) {
77
+ ctx.save()
78
+ ctx.beginPath()
79
+ ctx.arc(x, y, radius, startAngle, endAngle)
80
+ ctx.fillStyle = options.style || '#555'
81
+ ctx.globalAlpha = options.alpha || 1
82
+ ctx.lineTo(x, y)
83
+ ctx.fill()
84
+ ctx.closePath()
85
+ ctx.restore()
86
+ }
87
+
68
88
  getAutoStep(firstValue, lastValue, pointsLength) {
69
89
  return (lastValue - firstValue) / (pointsLength - 1)
70
90
  }
@@ -186,6 +206,52 @@ export default function chartCommon(target) {
186
206
  this.dom.appendChild(canvas)
187
207
  }
188
208
 
209
+ setLabelBox() {
210
+ const box = document.createElement('div')
211
+ box.className = 'chart-box'
212
+ this.labelBox = box
213
+ this.dom.appendChild(box)
214
+ }
215
+
216
+ drawLabels(labels, styles = DEFAULT_CHART_STYLES) {
217
+ if (labels.length <= 0) {
218
+ return
219
+ }
220
+ const { labelBox, handleLabelMouseOver, handleLabelMouseLeave } = this
221
+ this.dom.style.backgroundColor = this.bg
222
+
223
+ this.offLabels.forEach(off => off())
224
+ labelBox.innerHTML = ''
225
+
226
+ this.offLabels.length = 0
227
+
228
+ labels.forEach((label, i) => {
229
+
230
+ const div = document.createElement('div')
231
+ div.className = 'chart-box-item'
232
+
233
+ const square = document.createElement('div')
234
+ square.className = 'chart-box-square'
235
+ square.style.backgroundColor = styles[i]
236
+ div.appendChild(square)
237
+
238
+ const span = document.createElement('span')
239
+ span.textContent = label
240
+ div.appendChild(span)
241
+
242
+ labelBox.appendChild(div)
243
+
244
+ if (isFn(handleLabelMouseOver)) {
245
+ const off = this.addEvent(div, 'mouseover', event => this.handleLabelMouseOver(event, i))
246
+ this.offLabels.push(off)
247
+ }
248
+ if (isFn(handleLabelMouseLeave)) {
249
+ const off = this.addEvent(div, 'mouseleave', event => this.handleLabelMouseLeave(event, i))
250
+ this.offLabels.push(off)
251
+ }
252
+ })
253
+ }
254
+
189
255
  setCanvasFontSize(canvas, fontSize) {
190
256
  const ctx = canvas.getContext('2d')
191
257
  const args = ctx.font.split(' ')
@@ -25,7 +25,12 @@ export default function supportDom(target) {
25
25
 
26
26
  addEvent(dom, name, func) {
27
27
  dom.addEventListener(name, func)
28
- this._listeners.push({ dom, name, func })
28
+ const listener = { dom, name, func }
29
+ this._listeners.push(listener)
30
+
31
+ return () => {
32
+ this._listeners = this._listeners.filter(l => l !== listener)
33
+ }
29
34
  }
30
35
 
31
36
  removeEvents() {
@@ -14,9 +14,12 @@ import DateTimeRanger from './components/DateTimeRanger'
14
14
  import Datepicker from './components/Datepicker'
15
15
  import Dropdown from './components/Dropdown'
16
16
  import LineChart from './components/LineChart'
17
+ import Monthpicker from './components/Monthpicker'
18
+ import MonthMenu from './components/MonthMenu'
17
19
  import Menu from './components/Menu'
18
20
  import Modal from './components/Modal'
19
21
  import Navbar from './components/Navbar'
22
+ import PieChart from './components/PieChart'
20
23
  import Radio from './components/Radio'
21
24
  import SearchDropdown from './components/SearchDropdown'
22
25
  import Sidebar from './components/Sidebar'
@@ -50,9 +53,12 @@ export {
50
53
  Datepicker,
51
54
  Dropdown,
52
55
  LineChart,
56
+ Monthpicker,
57
+ MonthMenu,
53
58
  Menu,
54
59
  Modal,
55
60
  Navbar,
61
+ PieChart,
56
62
  Radio,
57
63
  SearchDropdown,
58
64
  Sidebar,
@@ -9,6 +9,8 @@ import toPixel from '@superlanding/topixel'
9
9
  // date-fns
10
10
  import addDays from 'date-fns/addDays'
11
11
  import addMonths from 'date-fns/addMonths'
12
+ import subYears from 'date-fns/subYears'
13
+ import addYears from 'date-fns/addYears'
12
14
  import compareAsc from 'date-fns/compareAsc'
13
15
  import endOfDay from 'date-fns/endOfDay'
14
16
  import endOfMonth from 'date-fns/endOfMonth'
@@ -18,9 +20,11 @@ import getHours from 'date-fns/getHours'
18
20
  import getMinutes from 'date-fns/getMinutes'
19
21
  import getMonth from 'date-fns/getMonth'
20
22
  import getSeconds from 'date-fns/getSeconds'
23
+ import setYear from 'date-fns/setYear'
21
24
  import getYear from 'date-fns/getYear'
22
25
  import parse from 'date-fns/parse'
23
26
  import set from 'date-fns/set'
27
+ import setMonth from 'date-fns/setMonth'
24
28
  import startOfDay from 'date-fns/startOfDay'
25
29
  import startOfMonth from 'date-fns/startOfMonth'
26
30
  import subMonths from 'date-fns/subMonths'
@@ -28,6 +32,7 @@ import toDate from 'date-fns/toDate'
28
32
  import { format } from 'date-fns-tz'
29
33
 
30
34
  // lodash
35
+ import chunk from 'lodash.chunk'
31
36
  import debounce from 'lodash.debounce'
32
37
  import isFunction from 'lodash.isfunction'
33
38
  import noop from 'lodash.noop'
@@ -50,6 +55,8 @@ export {
50
55
  // date-fns
51
56
  addDays,
52
57
  addMonths,
58
+ addYears,
59
+ subYears,
53
60
  compareAsc,
54
61
  endOfDay,
55
62
  endOfMonth,
@@ -59,9 +66,11 @@ export {
59
66
  getMinutes,
60
67
  getMonth,
61
68
  getSeconds,
69
+ setYear,
62
70
  getYear,
63
71
  parse,
64
72
  set,
73
+ setMonth,
65
74
  startOfDay,
66
75
  startOfMonth,
67
76
  subMonths,
@@ -69,6 +78,7 @@ export {
69
78
  format,
70
79
 
71
80
  // lodash
81
+ chunk,
72
82
  debounce,
73
83
  isFunction,
74
84
  noop,
@@ -0,0 +1,3 @@
1
+ export default function isFn(fn) {
2
+ return (typeof fn === 'function')
3
+ }
@@ -26,6 +26,7 @@
26
26
  @import './components/_checkbox';
27
27
  @import './components/_date-input';
28
28
  @import './components/_date-menu';
29
+ @import './components/_month-menu';
29
30
  @import './components/_date-time-ranger';
30
31
  @import './components/_datepicker';
31
32
  @import './components/_dropdown';
@@ -12,3 +12,23 @@
12
12
  z-index: 1;
13
13
  border: 1px solid #e8e8e8;
14
14
  }
15
+
16
+ .chart-box {
17
+ display: flex;
18
+ flex-wrap: wrap;
19
+ padding-left: 14px;
20
+ padding-right: 14px;
21
+ padding-bottom: 10px;
22
+ .chart-box-item {
23
+ cursor: default;
24
+ font-size: 12px;
25
+ display: inline-flex;
26
+ align-items: center;
27
+ margin-right: 1.4em;
28
+ .chart-box-square {
29
+ transform: translateY(1px);
30
+ @include size(10px);
31
+ margin-right: .5em;
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,70 @@
1
+ .month-menu {
2
+ display: none;
3
+ position: absolute;
4
+ box-shadow: 0 0 0 1px rgba(136, 152, 170, .1),
5
+ 0 15px 35px 0 rgba(49, 49, 93, .1),
6
+ 0 5px 15px 0 rgba(0, 0, 0, .08);
7
+ background-color: #fff;
8
+ white-space: nowrap;
9
+ &.static {
10
+ display: inline-block;
11
+ position: static;
12
+ }
13
+ .month-menu-content {
14
+ display: inline-block;
15
+ position: relative;
16
+ padding: 0 14px 14px 14px;
17
+ width: 252px;
18
+ }
19
+ .month-menu-caption {
20
+ display: flex;
21
+ justify-content: space-between;
22
+ font-size: 15px;
23
+ color: #3c4257;
24
+ text-align: center;
25
+ height: 50px;
26
+ line-height: 50px;
27
+ }
28
+ .month-menu-caption-btn {
29
+ border: 0;
30
+ background-color: transparent;
31
+ display: block;
32
+ @include size(50px);
33
+
34
+ .icon-chevron-left,
35
+ .icon-chevron-right {
36
+ font-size: 12px;
37
+ }
38
+ }
39
+ .month-menu-table {
40
+ font-size: 14px;
41
+ width: 100%;
42
+ margin-top: 2px;
43
+ th, td {
44
+ padding: 3px;
45
+ }
46
+ td {
47
+ text-align: center;
48
+ &.cell {
49
+ background-color: #f7fafc;
50
+ cursor: pointer;
51
+ border-width: 1px;
52
+ border-style: solid;
53
+ border-color: #e3e8ee;
54
+ }
55
+ &.cell.today {
56
+ background-color: #fffef4;
57
+ }
58
+ &.cell.selected {
59
+ background-color: #6c8eef;
60
+ color: #fff;
61
+ border-color: #6c8eef;
62
+ }
63
+ &.cell.selected-ex {
64
+ background-color: #5469d4;
65
+ color: #fff;
66
+ border-color: #5469d4;
67
+ }
68
+ }
69
+ }
70
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beyond-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.240
4
+ version: 0.0.246
5
5
  platform: ruby
6
6
  authors:
7
7
  - kmsheng
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-11-06 00:00:00.000000000 Z
12
+ date: 2020-11-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sassc
@@ -139,7 +139,11 @@ files:
139
139
  - src/js/components/LineChart.js
140
140
  - src/js/components/Menu.js
141
141
  - src/js/components/Modal.js
142
+ - src/js/components/MonthInput.js
143
+ - src/js/components/MonthMenu.js
144
+ - src/js/components/Monthpicker.js
142
145
  - src/js/components/Navbar.js
146
+ - src/js/components/PieChart.js
143
147
  - src/js/components/Radio.js
144
148
  - src/js/components/SearchDropdown.js
145
149
  - src/js/components/Sidebar.js
@@ -194,6 +198,7 @@ files:
194
198
  - src/js/utils/getKey.js
195
199
  - src/js/utils/index.js
196
200
  - src/js/utils/isDef.js
201
+ - src/js/utils/isFn.js
197
202
  - src/js/utils/isInt.js
198
203
  - src/js/utils/isStr.js
199
204
  - src/js/utils/isTouchDevice.js
@@ -233,6 +238,7 @@ files:
233
238
  - src/sass/components/_list.scss
234
239
  - src/sass/components/_mega-menu.scss
235
240
  - src/sass/components/_modal.scss
241
+ - src/sass/components/_month-menu.scss
236
242
  - src/sass/components/_nav.scss
237
243
  - src/sass/components/_navbar.scss
238
244
  - src/sass/components/_pagination.scss