beyond-rails 0.0.240 → 0.0.246

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: 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