feat: 后端全量更新 - 含所有本次需求
- Contract.php: 返回合约账户余额(balance_contract) - My.php: 地址管理增加BTC/ETH - AppContract.php: 一键平仓(closeall) - AppProxy.php: 代理专属注册链接 + 分级权限(L1/L2) - site.php: 手续费减半(0.018→0.009) - agent_permission_setup.sql: 代理权限SQL - crypto_news_crawler.py: 新闻自动采集脚本
This commit is contained in:
+344
@@ -0,0 +1,344 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dynamic Pie Chart</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
|
||||
<meta name="description" content="A GoJS pie chart that updates dynamically as counts change." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="../release/go.js"></script>
|
||||
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
|
||||
<script id="code">
|
||||
function init() {
|
||||
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
|
||||
var $ = go.GraphObject.make;
|
||||
var pieRadius = 100;
|
||||
|
||||
myDiagram =
|
||||
$(go.Diagram, "myDiagramDiv",
|
||||
{
|
||||
"textEditingTool.starting": go.TextEditingTool.SingleClick,
|
||||
"ModelChanged": onModelChanged,
|
||||
"undoManager.isEnabled": true
|
||||
}
|
||||
);
|
||||
|
||||
// When a count changes in our model, ensure we trigger a redrawing of each slice in the pie
|
||||
function onModelChanged(e) {
|
||||
if (e.change === go.ChangedEvent.Property && e.propertyName === "count") {
|
||||
var slicedata = e.object;
|
||||
var nodedata = findNodeDataForSlice(slicedata);
|
||||
if (nodedata) {
|
||||
// Update the count binding to force makeGeo/positionSlice
|
||||
myDiagram.model.updateTargetBindings(nodedata, "count");
|
||||
// If the count went to 0, hide the slice
|
||||
var sliceindex = nodedata.slices.indexOf(slicedata);
|
||||
var slice = myDiagram.findNodeForKey(nodedata.key).findObject("PIE").elt(sliceindex);
|
||||
var sliceshape = slice.findObject("SLICE");
|
||||
if (slicedata.count === 0)
|
||||
sliceshape.visible = false;
|
||||
else
|
||||
sliceshape.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sliceTemplate =
|
||||
$(go.Panel,
|
||||
$(go.Shape,
|
||||
{
|
||||
name: "SLICE",
|
||||
strokeWidth: 2, stroke: "transparent",
|
||||
isGeometryPositioned: true
|
||||
},
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("geometry", "", makeGeo)
|
||||
),
|
||||
new go.Binding("position", "", positionSlice),
|
||||
{ // Allow the user to "select" slices when clicking them
|
||||
click: function(e, slice) {
|
||||
var sliceShape = slice.findObject("SLICE");
|
||||
var oldskips = slice.diagram.skipsUndoManager;
|
||||
slice.diagram.skipsUndoManager = true;
|
||||
if (sliceShape.stroke === "transparent") {
|
||||
sliceShape.stroke = go.Brush.darkenBy(slice.data.color, 0.4);
|
||||
// Move the slice out from the pie when selected
|
||||
var nodedata = findNodeDataForSlice(slice.data);
|
||||
if (nodedata) {
|
||||
var sliceindex = nodedata.slices.indexOf(slice.data);
|
||||
var angles = getAngles(nodedata, sliceindex);
|
||||
if (angles.sweep !== 360) {
|
||||
var angle = angles.start + angles.sweep / 2;
|
||||
var offsetPoint = new go.Point(pieRadius / 10, 0);
|
||||
slice.position = offsetPoint.rotate(angle).offset(pieRadius / 10, pieRadius / 10);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sliceShape.stroke = "transparent";
|
||||
slice.position = new go.Point(pieRadius / 10, pieRadius / 10);
|
||||
}
|
||||
slice.diagram.skipsUndoManager = oldskips;
|
||||
}
|
||||
},
|
||||
{
|
||||
toolTip:
|
||||
$("ToolTip",
|
||||
{ "Border.fill": "lightgray" },
|
||||
$(go.TextBlock,
|
||||
{ font: "10pt Verdana, sans-serif", margin: 4 },
|
||||
new go.Binding("text", "", function(data) {
|
||||
// Display text and percentage rounded to 2 decimals
|
||||
var nodedata = findNodeDataForSlice(data);
|
||||
if (nodedata) {
|
||||
var percent = Math.round((data.count / getTotalCount(nodedata) * 100) * 100) / 100;
|
||||
return data.text + ": " + percent + "%";
|
||||
}
|
||||
return "";
|
||||
}))
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
var optionTemplate =
|
||||
$(go.Panel, "TableRow",
|
||||
$(go.TextBlock,
|
||||
{
|
||||
column: 0,
|
||||
font: "10pt Verdana, sans-serif", alignment: go.Spot.Left,
|
||||
margin: 5
|
||||
},
|
||||
new go.Binding("text")
|
||||
),
|
||||
$(go.Panel, "Auto",
|
||||
{ column: 1 },
|
||||
$(go.Shape, { fill: "#F2F2F2" }),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
font: "10pt Verdana, sans-serif",
|
||||
textAlign: "right", margin: 2,
|
||||
wrap: go.TextBlock.None, width: 40,
|
||||
editable: true, isMultiline: false,
|
||||
textValidation: isValidCount
|
||||
},
|
||||
new go.Binding("text", "count").makeTwoWay(function(count) { return parseInt(count, 10); })
|
||||
)
|
||||
),
|
||||
$(go.Panel, "Horizontal",
|
||||
{ column: 2 },
|
||||
$("Button",
|
||||
{
|
||||
click: incrementCount
|
||||
},
|
||||
$(go.Shape, "PlusLine", { margin: 3, desiredSize: new go.Size(7, 7) })
|
||||
),
|
||||
$("Button",
|
||||
{
|
||||
click: decrementCount
|
||||
},
|
||||
$(go.Shape, "MinusLine", { margin: 3, desiredSize: new go.Size(7, 7) })
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.nodeTemplate =
|
||||
$(go.Node, "Vertical",
|
||||
{ deletable: false },
|
||||
$(go.TextBlock,
|
||||
{ font: "11pt Verdana, sans-serif", margin: 5 },
|
||||
new go.Binding("text")
|
||||
),
|
||||
$(go.Panel, "Horizontal",
|
||||
$(go.Panel, "Position",
|
||||
{
|
||||
name: "PIE",
|
||||
// account for slices offsetting when selected so the node won't change size
|
||||
desiredSize: new go.Size(pieRadius * 2.2 + 5, pieRadius * 2.2 + 5),
|
||||
itemTemplate: sliceTemplate
|
||||
},
|
||||
new go.Binding("itemArray", "slices")
|
||||
),
|
||||
$(go.Panel, "Table",
|
||||
{
|
||||
margin: 5,
|
||||
itemTemplate: optionTemplate
|
||||
},
|
||||
new go.Binding("itemArray", "slices")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
myDiagram.model = $(go.Model,
|
||||
{
|
||||
copiesArrays: true,
|
||||
copiesArrayObjects: true,
|
||||
nodeDataArray:
|
||||
[
|
||||
{
|
||||
key: 0,
|
||||
text: "Sample Poll",
|
||||
slices: [
|
||||
{ text: "Option 1", count: 21, color: "#B378C1" },
|
||||
{ text: "Option 2", count: 11, color: "#F25F5C" },
|
||||
{ text: "Option 3", count: 5, color: "#FFE066" },
|
||||
{ text: "Option 4", count: 2, color: "#2B98C5" },
|
||||
{ text: "Option 5", count: 1, color: "#70C1B3" }
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Validation function for editing text
|
||||
function isValidCount(textblock, oldstr, newstr) {
|
||||
if (newstr === "") return false;
|
||||
var num = +newstr; // quick way to convert a string to a number
|
||||
return !isNaN(num) && Number.isInteger(num) && num >= 0;
|
||||
}
|
||||
|
||||
// Given some slice data, find the corresponding node data
|
||||
function findNodeDataForSlice(slice) {
|
||||
var arr = myDiagram.model.nodeDataArray;
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var data = arr[i];
|
||||
if (data.slices.indexOf(slice) >= 0) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeGeo(data) {
|
||||
var nodedata = findNodeDataForSlice(data);
|
||||
var sliceindex = nodedata.slices.indexOf(data);
|
||||
var angles = getAngles(nodedata, sliceindex);
|
||||
|
||||
// Constructing the Geomtery this way is much more efficient than calling go.GraphObject.make:
|
||||
return new go.Geometry()
|
||||
.add(new go.PathFigure(pieRadius, pieRadius) // start point
|
||||
.add(new go.PathSegment(go.PathSegment.Arc,
|
||||
angles.start, angles.sweep, // angles
|
||||
pieRadius, pieRadius, // center
|
||||
pieRadius, pieRadius) // radius
|
||||
.close()));
|
||||
}
|
||||
|
||||
// Ensure slices get the proper positioning after we update any counts
|
||||
function positionSlice(data, obj) {
|
||||
var nodedata = findNodeDataForSlice(data);
|
||||
var sliceindex = nodedata.slices.indexOf(data);
|
||||
var angles = getAngles(nodedata, sliceindex);
|
||||
|
||||
var selected = obj.findObject("SLICE").stroke !== "transparent";
|
||||
if (selected && angles.sweep !== 360) {
|
||||
var offsetPoint = new go.Point(pieRadius / 10, 0); // offset by 1/10 the radius
|
||||
offsetPoint = offsetPoint.rotate(angles.start + angles.sweep / 2); // rotate to the correct angle
|
||||
offsetPoint = offsetPoint.offset(pieRadius / 10, pieRadius / 10); // translate center toward middle of pie panel
|
||||
return offsetPoint;
|
||||
}
|
||||
return new go.Point(pieRadius / 10, pieRadius / 10);
|
||||
}
|
||||
|
||||
// This is a bit inefficient, but should be OK for normal-sized graphs with reasonable numbers of slices per node
|
||||
function findAllSelectedItems() {
|
||||
var slices = [];
|
||||
for (var nit = myDiagram.nodes; nit.next();) {
|
||||
var node = nit.value;
|
||||
var pie = node.findObject("PIE");
|
||||
if (pie) {
|
||||
for (var sit = pie.elements; sit.next();) {
|
||||
var slicepanel = sit.value;
|
||||
if (slicepanel.findObject("SLICE").stroke !== "transparent") slices.push(slicepanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
return slices;
|
||||
}
|
||||
|
||||
// Override the standard CommandHandler deleteSelection behavior.
|
||||
// If there are any selected slices, delete them instead of deleting any selected nodes or links.
|
||||
myDiagram.commandHandler.canDeleteSelection = function() {
|
||||
// True if there are any selected deletable nodes or links,
|
||||
// or if there are any selected slices within nodes
|
||||
return go.CommandHandler.prototype.canDeleteSelection.call(myDiagram.commandHandler) ||
|
||||
findAllSelectedItems().length > 0;
|
||||
};
|
||||
|
||||
myDiagram.commandHandler.deleteSelection = function() {
|
||||
var slices = findAllSelectedItems();
|
||||
if (slices.length > 0) { // if there are any selected slices, delete them
|
||||
myDiagram.startTransaction("delete slices");
|
||||
var nodeset = new go.Set();
|
||||
for (var i = 0; i < slices.length; i++) {
|
||||
var panel = slices[i];
|
||||
var nodedata = panel.part.data;
|
||||
var slicearray = nodedata.slices;
|
||||
var slicedata = panel.data;
|
||||
var sliceindex = slicearray.indexOf(slicedata);
|
||||
// Remove the slice from the model
|
||||
myDiagram.model.removeArrayItem(slicearray, sliceindex);
|
||||
nodeset.add(nodedata);
|
||||
}
|
||||
// Force geometries to be redrawn on any node that had slices deleted
|
||||
nodeset.each(function(data) {
|
||||
myDiagram.model.updateTargetBindings(data, "count");
|
||||
});
|
||||
myDiagram.commitTransaction("delete slices");
|
||||
} else { // otherwise just delete nodes and/or links, as usual
|
||||
go.CommandHandler.prototype.deleteSelection.call(myDiagram.commandHandler);
|
||||
}
|
||||
};
|
||||
|
||||
// Return total count of a given node
|
||||
function getTotalCount(nodedata) {
|
||||
var totCount = 0;
|
||||
for (var i = 0; i < nodedata.slices.length; i++) {
|
||||
totCount += nodedata.slices[i].count;
|
||||
}
|
||||
return totCount;
|
||||
}
|
||||
|
||||
// Determine start and sweep angles given some node data and the index of the slice
|
||||
function getAngles(nodedata, index) {
|
||||
var totCount = getTotalCount(nodedata);
|
||||
var startAngle = -90;
|
||||
for (var i = 0; i < index; i++) {
|
||||
startAngle += 360 * nodedata.slices[i].count / totCount;
|
||||
}
|
||||
return { "start": startAngle, "sweep": 360 * nodedata.slices[index].count / totCount };
|
||||
}
|
||||
|
||||
// When user hits + button, increment count on that option
|
||||
function incrementCount(e, obj) {
|
||||
myDiagram.model.startTransaction("increment count");
|
||||
var slicedata = obj.panel.panel.data;
|
||||
myDiagram.model.setDataProperty(slicedata, "count", slicedata.count + 1);
|
||||
myDiagram.model.commitTransaction("increment count");
|
||||
}
|
||||
|
||||
// When user hits - button, decrement count on that option
|
||||
function decrementCount(e, obj) {
|
||||
myDiagram.model.startTransaction("decrement count");
|
||||
var slicedata = obj.panel.panel.data;
|
||||
if (slicedata.count > 0)
|
||||
myDiagram.model.setDataProperty(slicedata, "count", slicedata.count - 1);
|
||||
myDiagram.model.commitTransaction("decrement count");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="init()">
|
||||
<div id="sample">
|
||||
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 500px;"></div>
|
||||
<p>
|
||||
This sample demonstrates the ability to build an updateable pie chart with selectable slices.
|
||||
The Geometry for each slice is built using a <a>PathFigure</a> with a <a>PathSegment,Arc</a>.
|
||||
Slices use a custom <b>click</b> function, which sets a stroke and offsets slices as they are selected.
|
||||
Functionality for "selection" and deletion of these slices is similar to the <a href="selectableFields.html">Selectable Fields sample</a>,
|
||||
using some overridden <a>CommandHandler</a> functions.
|
||||
Each slice also has a tooltip showing the text and percentage of votes.
|
||||
</p>
|
||||
<p>
|
||||
Poll results can be adjusted and the pie chart will automatically update to reflect any changes.
|
||||
This includes deleting selected slices, updating the count using a TextBlock, or using the +/- buttons.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user