beyond-rails 0.0.240 → 0.0.242

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: 0b3bbdec941b619698d3d2056d2f14b9e5ac05a9ceca5da77cae6ed3b92880e4
4
+ data.tar.gz: da7470c133596ff14a1f6223b44877e62c60d0b8519a7cceb1a2c60384aaece5
5
5
  SHA512:
6
- metadata.gz: 1a54840c057c320dce8f19ebefb6388877e20bcebdf728a50cc5b8193822cb2a948e55423ed3802d20f1fa6628486947c79c847ea63a5637462b04a1400633ef
7
- data.tar.gz: 399fd279bfc53c652babd56f10e481ce33bc994df4391588f26729825b971923ce51186ffe64e48238b3135e834b58131e0fd742ab1bad49107a0397f470339a
6
+ metadata.gz: eb5ad03f6463c486d3d4fd258893a89e597f9fba5638eb4a1da711885e93a8e27905b0d247c320534b92a28c62129609fd43bf56d4de5cdcc8b665365a57ce31
7
+ data.tar.gz: d8a09786eea15028edc4d9b38d5531e02c6656707f7f5dcf976aa0e21d2473c7657cf27fb770687b126668033c5170083c7c89422938da4b113ddd4752d18b8d
@@ -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,241 @@
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 { throttle } from '../utils'
6
+ import { DEFAULT_CHART_STYLES } from '../consts'
7
+
8
+ @supportDom
9
+ @chartCommon
10
+ export default class PieChart {
11
+
12
+ constructor(dom, options = {}) {
13
+ this.dom = dom
14
+ this.data = []
15
+ this.total = 0
16
+
17
+ this.options = options
18
+ this.height = options.height
19
+ this.width = options.width
20
+ this.padding = isDef(options.padding) ? options.padding : 30
21
+ this.styles = options.styles || DEFAULT_CHART_STYLES
22
+ this.bg = options.bg || '#fff'
23
+
24
+ this.init()
25
+ }
26
+
27
+ init() {
28
+ this.setDpr()
29
+ this.setDomSizeIfNeeded()
30
+ this.setCanvas()
31
+ this.setLabelBox()
32
+ this.clear()
33
+ this.bindMedia()
34
+ this.bindPointMouseOver()
35
+ }
36
+
37
+ get x() {
38
+ return this.width / 2
39
+ }
40
+
41
+ get y() {
42
+ return this.height / 2
43
+ }
44
+
45
+ get radius() {
46
+ return this.contentWidth / 2
47
+ }
48
+
49
+ get pieWidth() {
50
+ return this.radius * .3
51
+ }
52
+
53
+ get centerCircleRadius() {
54
+ return this.radius - this.pieWidth
55
+ }
56
+
57
+ get contentWidth() {
58
+ return this.width - (this.padding * 2)
59
+ }
60
+
61
+ get contentHeight() {
62
+ return this.height - (this.padding * 2)
63
+ }
64
+
65
+ bindPointMouseOver() {
66
+ if (isUndef(this.options.onPieMouseOver)) {
67
+ return
68
+ }
69
+ if (! ('onmousemove' in this.canvas)) {
70
+ return
71
+ }
72
+ this.addLayer()
73
+ const canvas = this.getHighestCanvas()
74
+ this.addEvent(canvas, 'mousemove', throttle(this.handleMouseMove.bind(this), 30))
75
+ }
76
+
77
+ draw() {
78
+ this.clear()
79
+ this.drawPie()
80
+ }
81
+
82
+ drawPie() {
83
+ const { x, y, radius, centerCircleRadius, ctx, total } = this
84
+
85
+ let distance = 0
86
+
87
+ this.data.forEach((row, i) => {
88
+
89
+ const ratio = (row.value / total)
90
+ const startAngle = Math.PI * (-.5 + 2 * distance)
91
+ const endAngle = Math.PI * (-.5 + 2 * (distance + ratio))
92
+
93
+ const options = {
94
+ style: this.styles[i]
95
+ }
96
+ this.fillArc(ctx, x, y, radius, startAngle, endAngle, options)
97
+ distance += ratio
98
+ })
99
+
100
+ this.fillCircle(ctx, x, y, centerCircleRadius, '#fff')
101
+ }
102
+
103
+ handleDprChange() {
104
+ this.setDpr()
105
+ this.refresh()
106
+ }
107
+
108
+ getPosAngle(x1, y1, x2, y2) {
109
+
110
+ let x = x2
111
+ let y = y2
112
+
113
+ if (x1 >= 0) {
114
+ x -= x1
115
+ }
116
+ if (y1 >= 0) {
117
+ y -= y1
118
+ }
119
+ if (x1 < 0) {
120
+ x += x1
121
+ }
122
+ if (y2 < 0) {
123
+ y += y2
124
+ }
125
+
126
+ let angle = Math.atan2(y, x) * 180 / Math.PI
127
+ if (angle < 0) {
128
+ angle = 180 + (180 + angle)
129
+ }
130
+ return (angle + 90) % 360
131
+ }
132
+
133
+ handleMouseMove(event) {
134
+
135
+ const { x, y } = this
136
+ const canvasMousePos = this.getMousePosInCanvas(event)
137
+ const mousePos = this.getMousePos(canvasMousePos)
138
+ const mouseX = canvasMousePos.x
139
+ const mouseY = canvasMousePos.y
140
+
141
+ const distanceToCenterPoint = Math.sqrt(Math.pow(mouseX - x, 2) +
142
+ Math.pow(mouseY - y, 2))
143
+
144
+ const inCenterCircle = distanceToCenterPoint <= this.centerCircleRadius
145
+
146
+ this.clearSliceGlow()
147
+
148
+ if (inCenterCircle) {
149
+ return this.options.onPieMouseOver(mousePos, null)
150
+ }
151
+
152
+ const inPieCircle = distanceToCenterPoint <= this.radius
153
+ if (! inPieCircle) {
154
+ return this.options.onPieMouseOver(mousePos, null)
155
+ }
156
+ const angle = this.getPosAngle(x, y, mouseX, mouseY)
157
+ const matchedRow = this.data.find(row => {
158
+ return (row.startAngle <= angle) && (angle <= row.endAngle)
159
+ })
160
+ if (matchedRow) {
161
+ this.drawSliceGlow(matchedRow)
162
+ this.options.onPieMouseOver(mousePos, matchedRow)
163
+ }
164
+ }
165
+
166
+ drawSliceGlow(row) {
167
+ const index = this.data.findIndex(r => r === row)
168
+ this.clearSliceGlow()
169
+ const { x, y, radius, centerCircleRadius } = this
170
+ const ctx = this.firstLayer.canvas.getContext('2d')
171
+
172
+ const delta = 90 * Math.PI / 180
173
+ const startAngle = (row.startAngle * Math.PI / 180) - delta
174
+ const endAngle = (row.endAngle * Math.PI / 180) - delta
175
+
176
+ const options = {
177
+ style: this.styles[index],
178
+ alpha: .3
179
+ }
180
+ const radiusDelta = (radius - centerCircleRadius) * .3
181
+ this.fillArc(ctx, x, y, radius + radiusDelta, startAngle, endAngle, options)
182
+ this.fillCircle(this.firstLayer.ctx, x, y, centerCircleRadius, '#fff')
183
+ }
184
+
185
+ clearSliceGlow() {
186
+ const ctx = this.firstLayer.canvas.getContext('2d')
187
+ ctx.clearRect(0, 0, this.width, this.height)
188
+ }
189
+
190
+ refresh() {
191
+ this.raf(() => {
192
+ this.clearCanvasSize(this.canvas)
193
+ this.layers.forEach(layer => this.clearCanvasSize(layer.canvas))
194
+ this.setDomSizeIfNeeded()
195
+ this.setCanvasSize(this.canvas)
196
+ this.layers.forEach(layer => this.setCanvasSize(layer.canvas))
197
+ this.draw()
198
+ })
199
+ }
200
+
201
+ setAngles(data) {
202
+ const { total } = this
203
+ let startAngle = 0
204
+ return data.map(row => {
205
+ const endAngle = startAngle + ((row.value / total) * 360)
206
+ const nextRow = Object.assign({}, row, { startAngle, endAngle })
207
+ startAngle = endAngle
208
+ return nextRow
209
+ })
210
+ }
211
+
212
+ handleLabelMouseOver(index) {
213
+ this.drawSliceGlow(this.data[index])
214
+ }
215
+
216
+ handleLabelMouseLeave(index) {
217
+ this.clearSliceGlow()
218
+ }
219
+
220
+ setData(data) {
221
+ this.total = data.reduce((t, row) => t + row.value, 0)
222
+ this.data = this.setAngles(data)
223
+ this.raf(() => {
224
+ const labels = this.data.map(row => row.label)
225
+ this.drawLabels(labels, this.styles)
226
+ this.draw()
227
+ })
228
+ }
229
+
230
+ destroy() {
231
+ const { dom, canvas } = this
232
+
233
+ this.unbindMedia()
234
+ this.removeAllLayers()
235
+
236
+ if (dom.contains(canvas)) {
237
+ dom.removeChild(canvas)
238
+ dom.style.removeProperty('position')
239
+ }
240
+ }
241
+ }
@@ -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', () => this.handleLabelMouseOver(i))
246
+ this.offLabels.push(off)
247
+ }
248
+ if (isFn(handleLabelMouseLeave)) {
249
+ const off = this.addEvent(div, 'mouseleave', () => this.handleLabelMouseLeave(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() {
@@ -17,6 +17,7 @@ import LineChart from './components/LineChart'
17
17
  import Menu from './components/Menu'
18
18
  import Modal from './components/Modal'
19
19
  import Navbar from './components/Navbar'
20
+ import PieChart from './components/PieChart'
20
21
  import Radio from './components/Radio'
21
22
  import SearchDropdown from './components/SearchDropdown'
22
23
  import Sidebar from './components/Sidebar'
@@ -53,6 +54,7 @@ export {
53
54
  Menu,
54
55
  Modal,
55
56
  Navbar,
57
+ PieChart,
56
58
  Radio,
57
59
  SearchDropdown,
58
60
  Sidebar,
@@ -0,0 +1,3 @@
1
+ export default function isFn(fn) {
2
+ return (typeof fn === 'function')
3
+ }
@@ -12,3 +12,24 @@
12
12
  z-index: 1;
13
13
  border: 1px solid #e8e8e8;
14
14
  }
15
+
16
+ .chart-box {
17
+ display: flex;
18
+ justify-content: center;
19
+ flex-wrap: wrap;
20
+ padding-left: 14px;
21
+ padding-right: 14px;
22
+ padding-bottom: 10px;
23
+ .chart-box-item {
24
+ cursor: default;
25
+ font-size: 12px;
26
+ display: inline-flex;
27
+ align-items: center;
28
+ margin-right: 1.4em;
29
+ .chart-box-square {
30
+ transform: translateY(1px);
31
+ @include size(10px);
32
+ margin-right: .5em;
33
+ }
34
+ }
35
+ }
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.242
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-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sassc
@@ -140,6 +140,7 @@ files:
140
140
  - src/js/components/Menu.js
141
141
  - src/js/components/Modal.js
142
142
  - src/js/components/Navbar.js
143
+ - src/js/components/PieChart.js
143
144
  - src/js/components/Radio.js
144
145
  - src/js/components/SearchDropdown.js
145
146
  - src/js/components/Sidebar.js
@@ -194,6 +195,7 @@ files:
194
195
  - src/js/utils/getKey.js
195
196
  - src/js/utils/index.js
196
197
  - src/js/utils/isDef.js
198
+ - src/js/utils/isFn.js
197
199
  - src/js/utils/isInt.js
198
200
  - src/js/utils/isStr.js
199
201
  - src/js/utils/isTouchDevice.js