357 lines
13 KiB
HTML
Generated
357 lines
13 KiB
HTML
Generated
|
|
<!--
|
|
Licensed to the Apache Software Foundation (ASF) under one
|
|
or more contributor license agreements. See the NOTICE file
|
|
distributed with this work for additional information
|
|
regarding copyright ownership. The ASF licenses this file
|
|
to you under the Apache License, Version 2.0 (the
|
|
"License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing,
|
|
software distributed under the License is distributed on an
|
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
KIND, either express or implied. See the License for the
|
|
specific language governing permissions and limitations
|
|
under the License.
|
|
-->
|
|
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<script src="lib/simpleRequire.js"></script>
|
|
<script src="lib/config.js"></script>
|
|
<script src="lib/jquery.min.js"></script>
|
|
<script src="lib/testHelper.js"></script>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<link rel="stylesheet" href="lib/reset.css" />
|
|
</head>
|
|
<body>
|
|
<style>
|
|
h1 {
|
|
line-height: 60px;
|
|
height: 60px;
|
|
background: #ddd;
|
|
text-align: center;
|
|
font-weight: bold;
|
|
font-size: 14px;
|
|
}
|
|
.test-chart {
|
|
height: 500px;
|
|
margin: 10px auto;
|
|
}
|
|
</style>
|
|
|
|
|
|
|
|
<h1>Hexagonal Binning</h1>
|
|
<div class="chart" id="hexagonal-binning"></div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
// Hexbin statistics code based on [d3-hexbin](https://github.com/d3/d3-hexbin)
|
|
function hexBinStatistics(points, r) {
|
|
var dx = r * 2 * Math.sin(Math.PI / 3)
|
|
var dy = r * 1.5;
|
|
var binsById = {};
|
|
var bins = [];
|
|
|
|
for (var i = 0, n = points.length; i < n; ++i) {
|
|
var point = points[i];
|
|
var px = point[0];
|
|
var py = point[1];
|
|
|
|
if (isNaN(px) || isNaN(py)) {
|
|
continue;
|
|
}
|
|
|
|
var pj = Math.round(py = py / dy);
|
|
var pi = Math.round(px = px / dx - (pj & 1) / 2);
|
|
var py1 = py - pj;
|
|
|
|
if (Math.abs(py1) * 3 > 1) {
|
|
var px1 = px - pi;
|
|
var pi2 = pi + (px < pi ? -1 : 1) / 2;
|
|
var pj2 = pj + (py < pj ? -1 : 1);
|
|
var px2 = px - pi2;
|
|
var py2 = py - pj2;
|
|
if (px1 * px1 + py1 * py1 > px2 * px2 + py2 * py2) {
|
|
pi = pi2 + (pj & 1 ? 1 : -1) / 2;
|
|
pj = pj2;
|
|
}
|
|
}
|
|
|
|
var id = pi + "-" + pj;
|
|
var bin = binsById[id];
|
|
if (bin) {
|
|
bin.points.push(point);
|
|
}
|
|
else {
|
|
bins.push(bin = binsById[id] = {points: [point]});
|
|
bin.x = (pi + (pj & 1) / 2) * dx;
|
|
bin.y = pj * dy;
|
|
}
|
|
}
|
|
|
|
var maxBinLen = -Infinity
|
|
for (var i = 0; i < bins.length; i++) {
|
|
maxBinLen = Math.max(maxBinLen, bins.length);
|
|
}
|
|
|
|
return {
|
|
maxBinLen: maxBinLen,
|
|
bins: bins
|
|
};
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
<script>
|
|
require([
|
|
'echarts',
|
|
'./data/kawhi-leonard-16-17-regular.json',
|
|
'./data/nba-court.json'
|
|
], function (echarts, shotData, nbaCourt) {
|
|
|
|
// 2006-2007 Regular Season
|
|
echarts.registerMap('nbaCourt', nbaCourt.borderGeoJSON);
|
|
|
|
var backgroundColor = '#333';
|
|
var hexagonRadiusInGeo = 1;
|
|
|
|
var hexBinResult = hexBinStatistics(
|
|
echarts.util.map(shotData.data, function (item) {
|
|
// "shot_made_flag" made missed
|
|
var made = item[echarts.util.indexOf(shotData.schema, 'shot_made_flag')];
|
|
return [
|
|
item[echarts.util.indexOf(shotData.schema, 'loc_x')],
|
|
item[echarts.util.indexOf(shotData.schema, 'loc_y')],
|
|
made === 'made' ? 1 : 0
|
|
];
|
|
}),
|
|
hexagonRadiusInGeo
|
|
);
|
|
|
|
var data = echarts.util.map(hexBinResult.bins, function (bin) {
|
|
var made = 0;
|
|
echarts.util.each(bin.points, function (point) {
|
|
made += point[2];
|
|
});
|
|
return [bin.x, bin.y, bin.points.length, (made / bin.points.length * 100).toFixed(2)];
|
|
});
|
|
|
|
function createItemRenderer(type) {
|
|
type = type || 'polygon';
|
|
return function renderItemHexBin(params, api) {
|
|
var center = api.coord([api.value(0), api.value(1)]);
|
|
var points = [];
|
|
var pointsBG = [];
|
|
|
|
var maxViewRadius = api.size([hexagonRadiusInGeo, 0])[0];
|
|
var minViewRadius = Math.min(maxViewRadius, 4);
|
|
var extentMax = Math.log(Math.sqrt(hexBinResult.maxBinLen));
|
|
var viewRadius = echarts.number.linearMap(
|
|
Math.log(Math.sqrt(api.value(2))),
|
|
[0, extentMax],
|
|
[minViewRadius, maxViewRadius]
|
|
);
|
|
|
|
var angle = Math.PI / 6;
|
|
for (var i = 0; i < 6; i++, angle += Math.PI / 3) {
|
|
points.push([
|
|
center[0] + viewRadius * Math.cos(angle),
|
|
center[1] + viewRadius * Math.sin(angle)
|
|
]);
|
|
pointsBG.push([
|
|
center[0] + maxViewRadius * Math.cos(angle),
|
|
center[1] + maxViewRadius * Math.sin(angle)
|
|
]);
|
|
}
|
|
|
|
return {
|
|
type: 'group',
|
|
children: [{
|
|
type,
|
|
shape: type === 'polygon' ? {
|
|
points: points
|
|
} : {
|
|
// Circle
|
|
cx: center[0],
|
|
cy: center[1],
|
|
r: viewRadius
|
|
},
|
|
style: {
|
|
stroke: '#ccc',
|
|
fill: api.visual('color'),
|
|
lineWidth: 0
|
|
}
|
|
}, {
|
|
type,
|
|
shape: type === 'polygon' ? {
|
|
points: pointsBG
|
|
} : {
|
|
// Circle
|
|
cx: center[0],
|
|
cy: center[1],
|
|
r: maxViewRadius
|
|
},
|
|
style: {
|
|
stroke: null,
|
|
fill: 'rgba(0,0,0,0.5)',
|
|
lineWidth: 0
|
|
},
|
|
z2: -19
|
|
}]
|
|
};
|
|
};
|
|
}
|
|
|
|
function renderItemNBACourt(param, api) {
|
|
return {
|
|
type: 'group',
|
|
children: echarts.util.map(nbaCourt.geometry, function (item) {
|
|
return {
|
|
type: item.type,
|
|
style: {
|
|
stroke: '#aaa',
|
|
fill: null,
|
|
lineWidth: 1.5
|
|
},
|
|
shape: {
|
|
points: echarts.util.map(item.points, api.coord)
|
|
}
|
|
};
|
|
})
|
|
};
|
|
}
|
|
|
|
var option = {
|
|
backgroundColor: backgroundColor,
|
|
aria: {
|
|
show: true
|
|
},
|
|
tooltip: {
|
|
backgroundColor: 'rgba(255,255,255,0.9)',
|
|
textStyle: {
|
|
color: '#333'
|
|
}
|
|
},
|
|
title: {
|
|
text: 'Kawhi Leonard',
|
|
subtext: '2016-2017 Regular Season',
|
|
backgroundColor: backgroundColor,
|
|
top: 10,
|
|
left: 10,
|
|
textStyle: {
|
|
color: '#eee'
|
|
}
|
|
},
|
|
legend: {
|
|
data: ['bar', 'error']
|
|
},
|
|
geo: {
|
|
left: 0,
|
|
right: 0,
|
|
top: 0,
|
|
bottom: 0,
|
|
roam: true,
|
|
silent: true,
|
|
itemStyle: {
|
|
normal: {
|
|
color: backgroundColor,
|
|
borderWidth: 0
|
|
}
|
|
},
|
|
map: 'nbaCourt'
|
|
},
|
|
visualMap: {
|
|
type: 'continuous',
|
|
orient: 'horizontal',
|
|
right: 30,
|
|
top: 40,
|
|
min: 0,
|
|
max: 100,
|
|
align: 'bottom',
|
|
text: [null, 'FG: '],
|
|
dimension: 3,
|
|
seriesIndex: 0,
|
|
calculable: true,
|
|
textStyle: {
|
|
color: '#eee'
|
|
},
|
|
formatter: '{value} %',
|
|
inRange: {
|
|
// color: ['rgba(241,222,158, 0.3)', 'rgba(241,222,158, 1)']
|
|
color: ['green', 'yellow']
|
|
}
|
|
},
|
|
series: [{
|
|
type: 'custom',
|
|
coordinateSystem: 'geo',
|
|
geoIndex: 0,
|
|
renderItem: createItemRenderer(),
|
|
dimensions: [null, null, 'Field Goals Attempted (hexagon size)', 'Field Goal Percentage (color)'],
|
|
encode: {
|
|
tooltip: [2, 3]
|
|
},
|
|
data: data
|
|
}, {
|
|
coordinateSystem: 'geo',
|
|
type: 'custom',
|
|
geoIndex: 0,
|
|
renderItem: renderItemNBACourt,
|
|
silent: true,
|
|
data: [0]
|
|
}]
|
|
};
|
|
|
|
// var width = 1000;
|
|
const myChart = testHelper.create(echarts, 'hexagonal-binning', {
|
|
option,
|
|
buttons: [{
|
|
text: 'Hexgon',
|
|
onClick: function() {
|
|
myChart.setOption({
|
|
series: [{
|
|
type: 'custom',
|
|
universalTransition: {
|
|
enabled: true
|
|
},
|
|
renderItem: createItemRenderer('polygon')
|
|
}]
|
|
});
|
|
}
|
|
}, {
|
|
text: 'Circle',
|
|
onClick: function() {
|
|
myChart.setOption({
|
|
series: [{
|
|
type: 'custom',
|
|
universalTransition: {
|
|
enabled: true
|
|
},
|
|
renderItem: createItemRenderer('circle')
|
|
}]
|
|
});
|
|
}
|
|
}]
|
|
// width: width,
|
|
// height: width * nbaCourt.height / nbaCourt.width
|
|
});
|
|
});
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</body>
|
|
</html> |