之前在一篇博文中捎帶介紹過這款工具,反響很好,還收到了兩位用戶的打賞,但受那篇博文的影響,並沒有被廣大小程序開發者所熟知,故寫一篇專門的,希望能有更多用戶不再被加速計、陀螺儀、設備方向的調試難題再刺痛。
加速計、陀螺儀、設備方向痛點
1、小程序不支持開發工具調試,只支持真機調試
2、真機調試輸出一堆日誌,來不及做日誌分析,就被頂上去
痛點解決方案
通過接收加速計、陀螺儀、設備方向返回的數據,實時繪製在折現圖表上,這樣一邊晃動手機,一邊可以觀察圖表中數據的變化,能夠很快分辨出動作所帶來的數據變化,方便調試。
實操效果
工具支持描述
加速計:X軸加速度、Y軸加速度、Z軸加速度的數據展示
陀螺儀:X軸角速度、Y軸角速度、Z軸角速度的數據展示
設備方向:α角、β角、γ角的數據展示
如下:
工具使用路徑
工具系列>>硬件數據監聽工具
單純使用,無需代碼
調用接口:
加速計:
wx.onAccelerometerChange(function (res) {
console.log("加速計數據變化", res)
})
陀螺儀api:
wx.startGyroscope({
interval: 'normal',
success: function (res) {
console.log('陀螺儀監聽成功', res)
},
fail: function (res) {
console.log('陀螺儀監聽失敗', res)
}
})
wx.onGyroscopeChange(function (res) {
console.log('陀螺儀數據變化',res)
})
設備方向api
wx.startDeviceMotionListening({
interval: 'normal',
success: function (res) {
console.log('設備方向監聽成功', res)
},
fail: function (res) {
console.log('設備方向監聽失敗', res)
}
})
wx.onDeviceMotionChange(function (res) {
console.log('設備方向數據變化',res)
})
圖表代碼:
exports.init = function (ctx, options) {
return new LineChart(ctx, options);
};
function LineChart(ctx, options) {
this.ctx = wx.createCanvasContext(ctx);
this.tipsCtx = options.tipsCtx ? wx.createCanvasContext(options.tipsCtx) : null;
this.options = Object.assign({
width: 320,
height: 200,
labelColor: '#888888',
axisColor: '#d0d0d0',
xUnit: '',
yUnit: '',
xAxis: [],
lines: [],
margin: 20,
fontSize: 12,
}, options, {
xAxisOffset: (options.margin || 20) + (options.fontSize || 12) * 1.5
});
this._attrs = {};
if (_.isEmpty(this.options.xAxis)) {
throw new Error('options.xAxis can not be empty');
}
if (_.isEmpty(this.options.lines)) {
throw new Error('options.lines can not be empty');
}
}
LineChart.prototype.draw = function () {
this._clear();
this._drawAxis();
this._drawLines();
this._draw();
};
LineChart.prototype.hideLine = function (index) {
let {
lines
} = this.options;
if (lines[index]) {
lines[index].hidden = true;
this.draw();
}
};
LineChart.prototype.showLine = function (index) {
let {
lines
} = this.options;
if (lines[index] && lines[index].hidden) {
lines[index].hidden = false;
this.draw();
}
};
LineChart.prototype.tipsByX = function (x) {
if (!this.options.tipsCtx) {
return;
}
let {
tipsIndex
} = this._attrs;
let index = this._indexByX(x);
if (index === tipsIndex) {
return;
}
this._attrs.tipsIndex = index;
this._tipsLineByIndex(index);
this._tipsLabelByIndex(index);
this.tipsCtx.draw();
};
LineChart.prototype.clearTips = function () {
if (!this.options.tipsCtx) {
return;
}
let {
width,
height
} = this.options;
this.tipsCtx.clearRect(0, 0, width, height);
this.tipsCtx.draw();
};
LineChart.prototype._tipsLabelByIndex = function (index) {
let {
xAxis,
xUnit,
yUnit,
margin
} = this.options;
let {
xOffset
} = this._attrs;
let ctx = this.tipsCtx;
ctx.setFontSize(12);
let title = xAxis[index] + xUnit;
let points = this._pointsByIndex(index);
points = _.map(points, item => ({
color: item.color,
text: `${item.value}${yUnit}`,
width: ctx.measureText(`${item.value}${yUnit}`).width + 15,
x: item.x
}));
let width = _.max(_.map(points, item => item.width));
width = Math.max(ctx.measureText(title).width, width) + 20;
let fromX = points[0].x - width;
if (fromX < xOffset) {
fromX = points[0].x;
}
let endX = fromX + width;
let fromY = margin;
let endY = margin + (points.length + 1) * 18;
// 背景
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.lineTo(fromX, endY);
ctx.lineTo(endX, endY);
ctx.lineTo(endX, fromY);
ctx.closePath();
ctx.globalAlpha = 0.4;
ctx.fillStyle = 'black';
ctx.fill();
// 文字
ctx.globalAlpha = 1;
ctx.fillStyle = 'white';
ctx.fillText(title, fromX + 10, fromY + 15);
_.each(points, (item, index) => {
let textY = fromY + 15 * (index + 2);
ctx.beginPath();
ctx.arc(fromX + 15, textY - 4, 5, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = item.color;
ctx.fill();
ctx.fillStyle = 'white';
ctx.fillText(item.text, fromX + 25, textY);
});
};
LineChart.prototype._tipsLineByIndex = function (index) {
let {
yOffset
} = this._attrs;
let {
margin,
labelColor
} = this.options;
let points = this._pointsByIndex(index);
let ctx = this.tipsCtx;
// 實心點
_.each(points, item => {
ctx.beginPath();
ctx.arc(item.x, item.y, 2, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = item.color;
ctx.fill();
});
ctx.beginPath();
ctx.lineWidth = 0.3;
ctx.strokeStyle = labelColor;
ctx.moveTo(points[0].x, yOffset);
ctx.lineTo(points[0].x, margin);
ctx.stroke();
};
LineChart.prototype._indexByX = function (x) {
let {
xOffset,
xStep
} = this._attrs;
let {
xAxis
} = this.options;
let index = 0;
if (x > xOffset) {
index = Math.min(Math.round((x - xOffset) / xStep), xAxis.length - 1);
}
return index;
};
LineChart.prototype._pointsByIndex = function (index) {
let {
xOffset,
xStep,
yOffset,
yStep
} = this._attrs;
return _.map(_.filter(this.options.lines, item => !item.hidden && !_.isUndefined(item.points[index])), item => ({
x: xOffset + index * xStep,
y: yOffset - item.points[index] * yStep,
value: item.points[index],
color: item.color
}));
};
LineChart.prototype._clear = function () {
let {
width,
height
} = this.options;
this.ctx.clearRect(0, 0, width, height);
};
LineChart.prototype._drawAxis = function () {
let {
width,
height,
lines,
labelColor,
axisColor,
xUnit,
yUnit,
xAxis,
margin,
xAxisOffset,
fontSize
} = this.options;
let ctx = this.ctx;
ctx.setFontSize(fontSize);
let yAxisLen = height - margin - xAxisOffset;
let yLabelCount = Math.floor(yAxisLen / 25);
let yMaxValue = _.max(_.map(lines, item => _.max(item.points))) || 1;
// 計算需要繪製的y軸label
let fixed = 0;
let yDelta = yMaxValue * 1.2 / yLabelCount;
if (yDelta < 1) {
fixed = Math.round(1 / yDelta).toString().length;
}
yDelta = Number(fixed === 0 ? Math.ceil(yDelta) : yDelta.toFixed(fixed));
let labels = [];
for (let i = 2; i <= yLabelCount; i++) {
labels.push(Number(((i - 1) * yDelta).toFixed(fixed)) + yUnit);
}
let xLabelMaxWidth = _.max(_.map(xAxis, item => ctx.measureText(item + xUnit).width));
let yLabelMaxWidth = _.max(_.map(labels, item => ctx.measureText(item).width)) + margin;
let xAxisLen = width - margin - yLabelMaxWidth;
let xOffset = yLabelMaxWidth;
let xStep = xAxisLen / (xAxis.length - 1);
let yOffset = margin + yAxisLen;
let yStep = yAxisLen / (yMaxValue * 1.2);
// 繪製x軸
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = axisColor;
ctx.moveTo(yLabelMaxWidth, height - xAxisOffset);
ctx.lineTo(width - margin, height - xAxisOffset);
ctx.stroke();
// 繪製x軸label
let xLabelCount = Math.floor(xAxisLen / xLabelMaxWidth);
let xLabelStep = Math.ceil(xAxis.length / xLabelCount);
// 需要被繪製的lable
let xLabel = _.filter(_.map(xAxis, (item, index) => ({
name: item + xUnit,
index: index
})), (item, index) => index % xLabelStep === 0);
_.each(xLabel, item => {
let xValue = xOffset + item.index * xStep - xLabelMaxWidth / 2 - 2;
ctx.fillStyle = labelColor;
ctx.fillText(item.name, xValue, height - margin);
});
// 繪製y軸label,以及水平標記線
_.each(labels, (item, index) => {
let xValue = (yLabelMaxWidth - ctx.measureText(item).width) - 5;
let yValue = yOffset - yStep * Number((index + 1) * yDelta).toFixed(fixed);
// 水平標記線
ctx.beginPath();
ctx.lineWidth = 0.8;
ctx.strokeStyle = axisColor;
ctx.moveTo(yLabelMaxWidth, yValue);
ctx.lineTo(width - margin, yValue);
ctx.stroke();
// label
ctx.strokeStyle = labelColor;
ctx.fillText(item, xValue, yValue + 4);
});
// 將這幾個數據存放在attrs上,繪製線的時候有用
Object.assign(this._attrs, {
xOffset,
yOffset,
xStep,
yStep
});
};
LineChart.prototype._drawLines = function () {
_.each(this.options.lines, item => {
if (item.hidden) {
return;
}
this._drawLine(item);
});
};
LineChart.prototype._drawLine = function (line) {
let {
xOffset,
yOffset,
xStep,
yStep
} = this._attrs;
let ctx = this.ctx;
let points = _.map(line.points, (item, index) => ({
x: xOffset + index * xStep,
y: yOffset - item * yStep
}));
// 與x軸的面積陰影
ctx.beginPath();
ctx.globalAlpha = 0.2;
ctx.fillStyle = line.color;
ctx.moveTo(xOffset, yOffset);
_.each(points, item => {
ctx.lineTo(item.x, item.y);
});
ctx.lineTo(xOffset + xStep * (points.length - 1), yOffset);
ctx.closePath();
ctx.fill();
// 線
ctx.beginPath();
ctx.globalAlpha = 1;
ctx.lineWidth = 1;
ctx.strokeStyle = line.color;
_.each(points, item => {
ctx.lineTo(item.x, item.y);
});
ctx.stroke();
// 空心點
_.each(points, item => {
ctx.beginPath();
ctx.arc(item.x, item.y, 2, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = 'white';
ctx.fill();
ctx.stroke();
});
};
LineChart.prototype._draw = function () {
if (this._timer) {
clearTimeout(this._timer);
}
this._timer = setTimeout(() => {
this.ctx.draw();
});
};