dragee-widgets
Version:
Widgets Based on Dragee Library
269 lines (232 loc) • 7.12 kB
JavaScript
import createCanvas from './utils/create-canvas'
import range from './utils/range'
import {
Draggable,
BoundToArc,
Point,
Rectangle,
getDistance
} from 'dragee'
import {
toRadian,
getPointFromRadialSystem,
getAngle,
normalizeAngle
} from './geometry/angles'
import EventEmitter from './EventEmitter'
const rnd = function() {
return Math.round(Math.random()*255)
}
const toHexString = function(digit) {
let str = digit.toString(16)
while (str.length < 2) {
str = '0' + str
}
return str
}
function randomColor() {
return `#${toHexString(rnd())}${toHexString(rnd())}${toHexString(rnd())}`
}
function getArrayWithBoundIndexes(index, length) {
const retIndexes = []
if (index !== -1) {
retIndexes.push(index)
retIndexes.push((index + 1) % length)
}
return retIndexes
}
export default class Chart extends EventEmitter {
constructor (area, elements, options={}) {
super(options)
const areaRectangle = Rectangle.fromElement(area, area)
this.options = Object.assign({
center: areaRectangle.getCenter(),
radius: areaRectangle.getMinSide() / 2,
touchRadius: areaRectangle.getMinSide() / 2,
boundAngle: Math.PI / 9,
fillStyles: range(0, elements.length).map(() => randomColor()),
initAngles: range(-90, 270, 360 / elements.length).map((angle) => toRadian(angle)),
limitImg: null,
limitImgOffset: new Point(0, 0)
}, options)
this.area = area
this.areaRectangle = areaRectangle
this.init(elements)
}
init(elements) {
this.canvas = createCanvas(this.area, this.areaRectangle)
this.context = this.canvas.getContext('2d')
this.draggables = elements.map((element, i) => {
const angle = this.options.initAngles[i]
const halfSize = Point.elementSize(element).mult(0.5)
const position = getPointFromRadialSystem(
angle,
this.options.touchRadius,
this.options.center.sub(halfSize)
)
return new Draggable(element, {
container: this.area,
bound: BoundToArc.bounding(
this.options.center.sub(halfSize),
this.options.touchRadius,
this.getBoundAngle(i, false),
this.getBoundAngle(i, true)
),
position: position,
on: {
'drag:move': () => this.draw()
}
})
})
this.isInit = true
this.draw()
}
updateAngles() {
this.angles = this.draggables.map((draggable) => {
const halfSize = draggable.getSize().mult(0.5)
return getAngle(this.options.center.sub(halfSize), draggable.position)
})
}
getBoundAngle(index, isClossing) {
const sign = isClossing ? 1 : -1
return () => {
let i = (index + sign) % this.angles.length
if (i < 0) {
i += this.angles.length
}
return normalizeAngle(this.angles[i] - sign * this.options.boundAngle)
}
}
draw() {
if (!this.isInit) {
return
}
this.updateAngles()
this.context.clearRect(0, 0, this.areaRectangle.size.x, this.areaRectangle.size.y)
this.draggables.forEach((_draggable, index) => {
this.drawArc(this.context, this.options.center, this.options.radius, index)
})
this.draggables.forEach((_draggable, index) => {
this.drawLimitImg(index)
})
this.emit('chart:draw')
}
createClone(element, options = {}) {
if (!this.isInit) {
return
}
const rectangle = Rectangle.fromElement(element, element)
const opts = Object.assign({
center: rectangle.getCenter(),
radius: rectangle.getMinSide() / 2,
fillStyles: this.options.fillStyles
}, options)
const canvas = createCanvas(element, rectangle)
const context = canvas.getContext('2d')
const cloneObj = {
draw: () => {
context.clearRect(0, 0, rectangle.size.x, rectangle.size.y)
this.draggables.forEach((_draggable, index) => {
this.drawArc(context, opts.center, opts.radius, index)
})
}
}
cloneObj.draw()
return cloneObj
}
getFillStyle(index) {
if (typeof this.options.fillStyles[index] === 'function') {
this.options.fillStyles[index] = this.options.fillStyles[index].call(this)
}
return this.options.fillStyles[index]
}
drawArc(context, center, radius, index) {
const startAngle = this.angles[index]
const endAngle = this.angles[(index+1) % this.angles.length]
const color = this.getFillStyle(index)
context.beginPath()
context.moveTo(center.x, center.y)
context.arc(center.x, center.y, radius, startAngle, endAngle, false)
context.lineTo(center.x, center.y)
context.closePath()
context.fillStyle = color
context.fill()
}
drawLimitImg(index) {
let point, img
if (this.options.limitImg) {
img = this.options.limitImg instanceof Array ? this.options.limitImg[index] : this.options.limitImg
}
if (img) {
const angle = normalizeAngle(this.angles[index])
point = new Point(0, -img.height / 2)
point = point.add(this.options.limitImgOffset)
this.context.translate(this.areaRectangle.size.x / 2, this.areaRectangle.size.y / 2)
this.context.rotate(angle)
this.context.drawImage(img, point.x, point.y)
this.context.setTransform(1, 0, 0, 1, 0, 0)
}
}
getAnglesDiff() {
const angles = this.angles.slice(1)
let baseAngle = this.angles[0]
angles.push(baseAngle)
return angles.map((angle) => {
const diffAngle = normalizeAngle(angle - baseAngle)
baseAngle = angle
return diffAngle
})
}
getPercent() {
return this.getAnglesDiff().map((diffAngle) => diffAngle / (2 * Math.PI))
}
getArcBisectrixs() {
return this.getAnglesDiff().map((diffAngle, i) => {
return normalizeAngle(this.angles[i] + diffAngle / 2)
})
}
getArcOnPoint(point) {
const angle = getAngle(this.options.center, point)
const radius = getDistance(this.options.center, point)
if (radius > this.options.radius) {
return -1
}
let offset = -1, i, j
for (i = 0; i < this.angles.length; i++) {
if (offset === -1 || this.angles[offset] > this.angles[i]) {
offset = i
}
}
for (i = 0, j = offset; i < this.angles.length; i++, j = (i + offset) % this.angles.length) {
if (angle < this.angles[j]) {
break
}
}
if (--j < 0) {
j += this.angles.length
}
return j
}
setAngles(angles) {
this.angles = angles
this.draggables.forEach((draggable, i) => {
const angle = this.angles[i]
const halfSize = draggable.getSize().mult(0.5)
const position = getPointFromRadialSystem(
angle,
this.options.touchRadius,
this.options.center.sub(halfSize)
)
draggable.moveAndSave(position)
})
this.draw()
}
setActiveArc(index) {
const enableIndexes = getArrayWithBoundIndexes(index, this.draggables.length)
this.activeArcIndex = index
this.draggables.forEach((draggable, i) => {
draggable.enable = enableIndexes.indexOf(i) !== -1
})
this.draw()
}
}