69 |
70 | {% endblock content %}
71 |
72 | {% block managers %}
73 |
74 | {% searchmanager id="session-event-search"
75 | search="sourcetype=minecraft_log player=$player$ action=player_connect OR action=player_disconnect"|token_safe %}
76 | {% searchmanager id="blocks-placed-search"
77 | search="sourcetype=minecraft_log player=$player$ action=block_placed | stats count"|token_safe %}
78 | {% searchmanager id="blocks-broken-search"
79 | search="sourcetype=minecraft_log player=$player$ action=block_broken | stats count"|token_safe %}
80 | {% searchmanager id="total-killed-search"
81 | search="sourcetype=minecraft_log killer=$player$ action=mob_died | stats count"|token_safe %}
82 |
83 | {% searchmanager id="time-played-search"
84 | search="sourcetype=minecraft_log player=$player$ | transaction player startswith=player_connect endswith=player_disconnect maxspan=2d | stats sum(duration) as total_time_sec | eval hours_played=total_time_sec/60.0"|token_safe %}
85 |
86 | {% searchmanager id="building-blocks-search"
87 | search="sourcetype=minecraft_log action=block_placed player=$player$ block_type!=TORCH block_type!=DIRT block_type!=CROPS | top limit=5 block_type"|token_safe %}
88 |
89 | {% searchmanager id="mining-blocks-search"
90 | search="sourcetype=minecraft_log action=block_broken player=$player$ block_type!=TORCH block_type!=DIRT block_type!=CROPS | top limit=5 block_type"|token_safe %}
91 |
92 | {% searchmanager id="mob-kills-search"
93 | search="sourcetype=minecraft_log killer=$player$ action=mob_died | top limit=5 victim"|token_safe %}
94 |
95 | {% searchmanager id="player-kills-search"
96 | search="sourcetype=minecraft_log action=player_died victim=$player$ killer!=lava killer!=fire killer!=fire_tick killer!=drowning killer!=suffocation killer!=fall killer!=suicide | timechart count by killer"|token_safe %}
97 |
98 | {% searchmanager id="player-search"
99 | search="sourcetype=minecraft_log | dedup player | table player" %}
100 |
101 | {% endblock managers %}
102 |
103 | {% block js %}
104 |
105 |
131 |
132 | {% endblock js %}
--------------------------------------------------------------------------------
/shared-mc/src/main/java/com/splunk/sharedmc/SingleSplunkConnection.java:
--------------------------------------------------------------------------------
1 | package com.splunk.sharedmc;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.security.KeyManagementException;
6 | import java.security.KeyStoreException;
7 | import java.security.NoSuchAlgorithmException;
8 | import java.util.Calendar;
9 |
10 | //import org.apache.http.client.methods.CloseableHttpResponse;
11 | //import org.apache.http.client.methods.HttpPost;
12 | //import org.apache.http.entity.ContentType;
13 | //import org.apache.http.entity.StringEntity;
14 | //import org.apache.http.impl.client.CloseableHttpClient;
15 | //import org.apache.http.impl.client.HttpClients;
16 | import org.apache.logging.log4j.LogManager;
17 | import org.apache.logging.log4j.Logger;
18 |
19 | import org.apache.hc.core5.http.ContentType;
20 | import org.apache.hc.core5.http.io.entity.StringEntity;
21 | import org.apache.hc.client5.http.classic.methods.HttpPost;
22 | import org.apache.hc.client5.http.impl.classic.HttpClients;
23 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
24 | import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
25 | import org.apache.hc.core5.io.CloseMode;
26 |
27 | import org.json.simple.JSONObject;
28 |
29 | /**
30 | * Knows a single Splunk instance by its host:port and forwards data to it.
31 | */
32 | public class SingleSplunkConnection implements SplunkConnection, Runnable {
33 | private static final String LOGGER_PREFIX = "SplunkConnection - ";
34 | private static final String DEFAULT_RECONNECT_TIME = "10";
35 | private String BASE_URL = "http://%s:%s/services/collector/event/1.0";
36 |
37 | /**
38 | * Interval in seconds between attempts to connect to Splunk.
39 | */
40 | private static final int RECONNECT_TIME =
41 | Integer.valueOf(System.getProperty("splunk_mc.reconnect_time", DEFAULT_RECONNECT_TIME));
42 |
43 | private final Logger logger;
44 | private final String url;
45 |
46 | private CloseableHttpClient httpClient;
47 | private CloseableHttpResponse response;
48 |
49 | private String token;
50 |
51 | // lazy
52 | private StringBuilder messagesToSend = new StringBuilder();
53 | private StringBuilder messagesOnRunway;
54 |
55 | /**
56 | * Constructor. Determines which Splunk instance this will connect to based on the host:port passed in. Set up a
57 | * shutdown hook to send any remaining messages to Splunk on close.
58 | *
59 | * @param host Host of Splunk to connect to.
60 | * @param port Port of Splunk to connect to.
61 | * @param startImmediately If true, creates a thread and starts this Splunk on construction.
62 | */
63 | public SingleSplunkConnection(String host, int port, String token, boolean startImmediately) {
64 | logger = LogManager.getLogger(LOGGER_PREFIX + host + ':' + port);
65 | this.token = token;
66 | url = String.format(BASE_URL, host, port);
67 |
68 | addFlushShutdownHook();
69 |
70 | if (startImmediately) {
71 | new Thread(this).start();
72 | }
73 | }
74 |
75 | @Override
76 | public void run() {
77 | while (true) {
78 | sendData();
79 | try {
80 | Thread.sleep(1000 * RECONNECT_TIME);
81 | } catch (final InterruptedException e) {
82 | //eat exception.
83 | }
84 | }
85 | }
86 |
87 | /**
88 | * Queues up a message to send to this Spunk connections' Splunk instance.
89 | *
90 | * @param message The message to send.
91 | */
92 | @Override
93 | public void sendToSplunk(String message) {
94 | JSONObject event = new JSONObject();
95 | //message = Calendar.getInstance().getTime().toString() + ' ' + message;
96 | event.put("event", message);
97 |
98 | messagesToSend.append(event.toString());
99 | }
100 |
101 | private boolean sendData() {
102 | boolean success = false;
103 | // probably a better way to do this.
104 | if (messagesOnRunway == null && messagesToSend.length() > 0) {
105 | messagesOnRunway = messagesToSend;
106 | messagesToSend = new StringBuilder();
107 | }else{
108 | // no messages to send, so safe to say messages have been sent.
109 | return messagesOnRunway == null;
110 | }
111 | try {
112 | logger.info("Sending data to splunk...");
113 | httpClient = HttpClients.createDefault();
114 | HttpPost post = new HttpPost(url);
115 | post.setHeader("Authorization", "Splunk " + token);
116 | StringEntity entity = new StringEntity(messagesOnRunway.toString(), ContentType.APPLICATION_JSON);
117 | post.setEntity(entity);
118 | response = httpClient.execute(post);
119 | int responseCode = response.getCode();
120 |
121 | if (responseCode > 199 && responseCode < 300) {
122 | messagesOnRunway = null;
123 | success = true;
124 | } else {
125 | ByteArrayOutputStream outstream = new ByteArrayOutputStream();
126 | response.getEntity().writeTo(outstream);
127 | byte[] responseBody = outstream.toByteArray();
128 | logger.error(new String(responseBody));
129 | }
130 |
131 | //post.completed();
132 | } catch (final IOException e) {
133 | logger.error("Unable to send message!", e);
134 | success = false;
135 | }finally{
136 | httpClient.close(CloseMode.GRACEFUL);
137 | response.close(CloseMode.GRACEFUL);
138 | }
139 |
140 | return success;
141 | }
142 |
143 | /**
144 | * Adds a shutdown hook that flushes this classes data buffer ({@code data}) by sending it to Splunk.
145 | */
146 | private void addFlushShutdownHook() {
147 | Runtime.getRuntime().addShutdownHook(
148 | new Thread() {
149 | @Override
150 | public void run() {
151 | logger.info("Shutting down: attempting to send remaining data.");
152 | if (sendData()) {
153 | logger.info("Remaining data sent!");
154 | } else {
155 | logger.error("Couldn't send all remaining data to Splunk!");
156 | // TODO: Write data to a log file, 'unsent_data.splunk' or some such...
157 | }
158 | }
159 | });
160 | }
161 |
162 | private void initHttpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
163 |
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Minecraft App
2 |
3 | ### Version 1.0
4 |
5 | The Minecraft App lets you visualize the minecraft world from the guts side. Wondering how many blocks have been dug up by your buddies? Not a problem. Wondering who's found the most diamonds? Yep, got it covered. Have you been planting enough wheat? Carrots? Pototoes? The Minecraft App will let you know.
6 |
7 | ### Release Notes
8 |
9 | * Uses the new Splunk HTTP Event Collector as it's input instead of TCP.
10 | * Now supports Spigot and Forge as well as Craftbukkit as a unified plugin jar.
11 | * Plugin will cache items that the server does not acknowledge so restarting Splunk is no longer an issue
12 | * Ore images in the "Mined Blocks" page of the app now display correctly.
13 | * Minor bug fixes.
14 |
15 | ### Getting Started
16 | This section provides information about installing and using the Minecraft App.
17 |
18 | #### Requirements
19 |
20 | * Operating System: Windows, Linux, or Mac OS X.
21 | * Web browsers: Latest versions of Chrome, Safari, or Firefox, Internet Explorer 9 or later.
22 | * Craftbukkit, Spigot or Forge
23 | * [Craftbukkit](http://bukkit.org/)
24 | * [Spigot](https://www.spigotmc.org)
25 | * [Forge](http://www.minecraftforge.net/)
26 | * LogToSplunk Plugin: The log to splunk plugin that allows input of more detailed minecraft data to splunk from [CraftBukkit](http://dev.bukkit.org/bukkit-plugins/logtosplunk/)
27 | * The Splunk Web Framework: The Web Framework is included in Splunk 6 and is available for download for Splunk 5 from the
28 | [Splunk Developer Portal](http://dev.splunk.com/view/webframework-standalone/SP-CAAAEMA).
29 | * Minecraft Overviewer (Optional): The Google Maps based minecraft word renderer from [Overviewer](http://overviewer.org)
30 |
31 | #### Installing the Minecraft App
32 | The Minecraft App is built as a Splunk App on the Splunk Web Framework and must be installed on top of it.
33 |
34 | ##### Installing from Splunk Web
35 | If you downloaded the Minecraft App from [Splunk Apps](http://apps.splunk.com), you can install the app within Splunk Web.
36 |
37 | * For more, see [Where to get more apps and add-ons](http://docs.splunk.com/Documentation/Splunk/latest/Admin/Wheretogetmoreapps).
38 |
39 | ##### Installing from a ZIP Source File
40 |
41 | 1. [Download and unzip the Minecraft App](https://github.com/splunk/minecraft-app/archive/develop.zip)
42 | or clone the repository from [GitHub](https://github.com/splunk/minecraft-app.git).
43 | 2. Copy the entire `/minecraft-app` subdirectory into `$SPLUNK_HOME/etc/apps/`.
44 | 3. Restart Splunk.
45 | 4. In Splunk Web, navigate to the Minecraft App (*http://localhost:8000/dj/minecraft-app*).
46 |
47 | #### Event Collector Configuration
48 |
49 | 1. Confgiure the Splunk Http Event Collector as noted in the [Documentation](http://dev.splunk.com/view/event-collector/SP-CAAAE6M) for whatever port you like.
50 | 2. Ensure firewalls and NAT are properly configured if applicable
51 | 3. Take note of the application key as you will need it later on
52 |
53 |
54 | #### Installing the LogToSplunk Plugin
55 |
56 | 1. Copy the LogToSplunk jar from the app tgz or app directory into your craftbukkit server's `plugins` directory.
57 | 2. Create a `config` directory in the root server folder (the directory that contains the `plugins` folder).
58 | 3. Create and edit a `splunk.properties` text file in the `config` directory created in 3. Replace the port and application in your `splunk.properties` and adjust other options as necessary.
59 |
60 | * Use the app token from the Event Collector Configuration above for this property `splunk.craft.token=BEEFCAFE-1337-F00D-8BDA-2410D44E3453`
61 |
62 | * If you're running splunk on a separate machine from your minecraft server update this property `splunk.craft.connection.host=127.0.0.1`
63 |
64 | * Use the app token from the Event Collector Configuration above for this property `splunk.craft.connection.port=8088`
65 |
66 | * If you wish to log the output to a local log as well set this property to "true" `mod.splunk.enable.consolelog=false`
67 |
68 | #### Configuring The Livemap
69 |
70 | 1. Download and configure Overview as described int the [Overviewer Docs](http://docs.overviewer.org/en/latest/)
71 | 2. Serve the overviewer via a webserver like [Apache](http://httpd.apache.org) or [IIS](http://www.iis.net). Many operating systems have a web service built in that just needs to be enabled.
72 | 3. Create an initial render from overviewer with at least one of the "Normal","Lighting", and "Night" options
73 | 4. Copy the overviewer.css,overviewer.js, and overviewerConfig.js scripts from the base render directory to `$SPLUNK_HOME/etc/apps/minecraft-app/django/minecraft-app/static/minecraft-app/` on your splunk server.
74 | 5. Edit `$SPLUNK_HOME/etc/apps/minecraft-app/django/minecraft-app/static/minecraft-app/overviewerConfig.js` and modify the path variable of each tileset object to include your webserver path. For example, change `"path": "world-normal"` to `"path": "http://webserver:81/world-normal"`. The external hostname must be used in order for the map to be visible to clients. Using "localhost" as the webserver will not work as the minecraft app does not reserve the map, it simply redirects to it.
75 |
76 | NOTE: The minecraft-app does not refresh overviewer renders automatically. This will need to be scheduled by another service (ie. cron or task scheduler).
77 |
78 |
79 | #### Known Issues
80 |
81 | 1) Time calculations and active players may be mis-reported if player disconnects are not logged properly (ie. due to a server crash). Orphaned sessions may be estimated by running sessions from connection to the subsequent server start.
82 | 2) The live map may appear to "shift" as the minecraft world expands and overviewer resets it's origin in future renders. This can be corrected by recopying and modifying the overviewerConfig.js script with the same steps as the installation.
83 |
84 |
85 |
86 | ## Documentation and resources
87 |
88 | When you need to know more:
89 |
90 | * For Overviewer documentation, see [Overviewer](http://overviewer.org)
91 |
92 | * For Spigt documentation, see [Spigot](https://www.spigotmc.org)
93 |
94 | * For Forge documentation, see [Forge](http://www.minecraftforge.net/)
95 |
96 | * For CraftBukkit documentation, see [Craftbukkit](http://bukkit.org/)
97 |
98 | * For all things developer with Splunk, your main resource is the [Splunk Developer Portal](http://dev.splunk.com).
99 |
100 | * For component reference documentation, see the [Splunk Web Framework Reference](http://docs.splunk.com/Documentation/WebFramework).
101 |
102 | * For more about Splunk in general, see [Splunk>Docs](http://docs.splunk.com/Documentation/Splunk).
103 |
104 |
105 | ### How to contribute
106 |
107 | If you would like to contribute to the Minecraft App, go here for more information:
108 |
109 | * [Minecraft App Github](https://github.com/splunk/minecraft-app)
110 |
111 | Please feel free to open issues and provide feedback through GitHub Issues.
112 |
113 | ## License
114 | The Minecraft_App is licensed under the Apache License 2.0. Details can be found in the LICENSE file.
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/forge/src/main/java/com/splunk/forge/event_loggers/PlayerEventLogger.java:
--------------------------------------------------------------------------------
1 | package com.splunk.forge.event_loggers;
2 |
3 | import java.util.Properties;
4 |
5 | import com.google.common.cache.Cache;
6 | import com.google.common.cache.CacheBuilder;
7 | import com.google.common.cache.CacheLoader;
8 | import com.splunk.sharedmc.Point3dLong;
9 | import com.splunk.sharedmc.event_loggers.AbstractEventLogger;
10 | import com.splunk.sharedmc.loggable_events.LoggablePlayerEvent;
11 |
12 | import net.minecraft.entity.player.EntityPlayer;
13 | import net.minecraft.util.Vec3;
14 | import net.minecraft.world.World;
15 | import net.minecraftforge.event.ServerChatEvent;
16 | import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
17 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
18 | import net.minecraftforge.fml.common.gameevent.PlayerEvent;
19 | import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent;
20 | import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedOutEvent;
21 | import net.minecraftforge.fml.relauncher.Side;
22 | import net.minecraftforge.fml.relauncher.SideOnly;
23 |
24 | /**
25 | * Handles the logging of player events.
26 | */
27 | public class PlayerEventLogger extends AbstractEventLogger {
28 | public static final double GRANULARITY = 1.5;
29 | public static final int MAX_PLAYERS = 128;
30 |
31 | /**
32 | * Keeps track players last positions, in a guava cache for it's eviction policy.
33 | */
34 | private final Cache lastKnownCoordinates = CacheBuilder.newBuilder().maximumSize(MAX_PLAYERS).build(
35 | new CacheLoader() {
36 | @Override
37 | public Vec3 load(String key) throws Exception {
38 | return lastKnownCoordinates.getIfPresent(key);
39 | }
40 | });
41 |
42 | /**
43 | * Constructs a new PlayerEventLogger.
44 | *
45 | * @param props Properties to configure this EventLogger with.
46 | */
47 | public PlayerEventLogger(Properties props) {
48 | super(props);
49 | }
50 |
51 | /**
52 | * Logs to Splunk when a player logs in.
53 | *
54 | * @param event The captured event.
55 | */
56 | @SubscribeEvent
57 | @SideOnly(Side.SERVER)
58 | public void onPlayerConnect(PlayerLoggedInEvent event) {
59 | logAndSend(
60 | generateLoggablePlayerEvent(event, LoggablePlayerEvent.PlayerEventAction.PLAYER_CONNECT, null, null));
61 | }
62 |
63 | /**
64 | * Logs to Splunk when a player logs out.
65 | *
66 | * @param event The captured event.
67 | */
68 | @SubscribeEvent
69 | @SideOnly(Side.SERVER)
70 | public void onPlayerDisconnect(PlayerLoggedOutEvent event) {
71 | logAndSend(
72 | generateLoggablePlayerEvent(
73 | event, LoggablePlayerEvent.PlayerEventAction.PLAYER_DISCONNECT, null, null));
74 | }
75 |
76 | /**
77 | * Logs to Splunk when a player chats and what they chat.
78 | *
79 | * @param chatEvent The captured chat event.
80 | */
81 | @SubscribeEvent
82 | @SideOnly(Side.SERVER)
83 | public void onPlayerChat(ServerChatEvent chatEvent) {
84 | logAndSend(
85 | generateLoggablePlayerEvent(chatEvent, LoggablePlayerEvent.PlayerEventAction.CHAT, chatEvent.message));
86 | }
87 |
88 | private static LoggablePlayerEvent generateLoggablePlayerEvent(
89 | PlayerEvent event, LoggablePlayerEvent.PlayerEventAction actionType, String reason, String message) {
90 | final World world = event.player.getEntityWorld();
91 | final long worldTime = world.getWorldTime();
92 | final String worldName = world.getWorldInfo().getWorldName();
93 | final Vec3 playerPos = event.player.getPositionVector();
94 | final Point3dLong coordinates = new Point3dLong(playerPos.xCoord, playerPos.yCoord, playerPos.zCoord);
95 | final LoggablePlayerEvent loggable = new LoggablePlayerEvent(actionType, worldTime, worldName, coordinates);
96 | loggable.setPlayerName(event.player.getDisplayNameString());
97 | loggable.setReason(reason);
98 | loggable.setMessage(message);
99 |
100 | return loggable;
101 | }
102 |
103 | private static LoggablePlayerEvent generateLoggablePlayerEvent(
104 | ServerChatEvent event, LoggablePlayerEvent.PlayerEventAction actionType, String message) {
105 | final World world = event.player.getEntityWorld();
106 | final long worldTime = world.getWorldTime();
107 | final String worldName = world.getWorldInfo().getWorldName();
108 | final Vec3 playerPos = event.player.getPositionVector();
109 | final Point3dLong coordinates = new Point3dLong(playerPos.xCoord, playerPos.yCoord, playerPos.zCoord);
110 | final LoggablePlayerEvent loggable = new LoggablePlayerEvent(actionType, worldTime, worldName, coordinates);
111 | loggable.setPlayerName(event.player.getDisplayNameString());
112 | loggable.setMessage(message);
113 |
114 | return loggable;
115 | }
116 |
117 | /**
118 | * Living update seems to get called about 10x/sec. We check if the update belongs to a player and if so we check if
119 | * the players position has changed significantly based on {@code GRANULARITY}.
120 | *
121 | * @param playerMove The captured event.
122 | */
123 | @SubscribeEvent
124 | @SideOnly(Side.SERVER)
125 | public void onPlayerStatusReported(LivingUpdateEvent playerMove) {
126 | if (playerMove.entity instanceof EntityPlayer) {
127 | final String playerName = ((EntityPlayer) playerMove.entity).getDisplayNameString();
128 | final Vec3 playerPos = playerMove.entity.getPositionVector();
129 |
130 | //Don't log if position hasn't changed significantly.
131 | final Vec3 lastCoords = lastKnownCoordinates.getIfPresent(playerName);
132 | if (lastCoords != null && playerPos.distanceTo(lastCoords) < GRANULARITY) {
133 | return;
134 | }
135 |
136 | lastKnownCoordinates.put(playerName, playerPos);
137 | final Point3dLong coordinates = new Point3dLong(playerPos.xCoord, playerPos.yCoord, playerPos.zCoord);
138 |
139 | final World world = playerMove.entity.getEntityWorld();
140 | final long worldTime = world.getWorldTime();
141 | final String worldName = world.getWorldInfo().getWorldName();
142 |
143 | // TODO: this needs to be in the from/to format.
144 |
145 | LoggablePlayerEvent playerEvent =new LoggablePlayerEvent(
146 | LoggablePlayerEvent.PlayerEventAction.LOCATION, worldTime, worldName, coordinates)
147 | .setPlayerName(playerName);
148 |
149 | if(lastCoords != null){
150 | playerEvent.setFrom(new Point3dLong(lastCoords.xCoord, lastCoords.yCoord, lastCoords.zCoord));
151 | playerEvent.setTo(coordinates);
152 | logAndSend(playerEvent);
153 | }
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/django/minecraft-app/templates/livemap.html:
--------------------------------------------------------------------------------
1 | {% extends "splunkdj:base_with_app_bar.html" %}
2 |
3 | {% load splunkmvc %}
4 |
5 | {% block title %}{{app_label}}{% endblock title %}
6 |
7 | {% block css %}
8 |
9 |
10 |
11 | {% endblock css %}
12 |
13 | {% block content %}
14 |
15 |
127 | {% endblock content %}
128 |
129 | {% block managers %}
130 |
131 | {% searchmanager id="most-mined-search" search="sourcetype=minecraft_log action=block_broken block_type!=TORCH block_type!=CROPS | top limit=5 block_type" %}
132 | {% searchmanager id="logging-search" search="sourcetype=minecraft_log action=block_broken block_type=*_LOG | stats count by block_type" %}
133 | {% searchmanager id="agriculture-search" search="sourcetype=minecraft_log action=block_broken AND (block_type=potato OR block_type=carrot OR block_type=watermelon OR block_type=pumpkin OR block_type=crops) | stats count by block_type" %}
134 |
135 | {% searchmanager id="coal-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=COAL_ORE | stats count" %}
136 | {% searchmanager id="redstone-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=REDSTONE_ORE OR block_type=GLOWING_REDSTONE_ORE | stats count" %}
137 | {% searchmanager id="lapis-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=LAPIS_ORE | stats count" %}
138 | {% searchmanager id="iron-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=IRON_ORE | stats count" %}
139 | {% searchmanager id="gold-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=GOLD_ORE | stats count" %}
140 | {% searchmanager id="diamond-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=DIAMOND_ORE | stats count" %}
141 | {% searchmanager id="emerald-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=EMERALD_ORE | stats count" %}
142 | {% searchmanager id="quartz-ore-search" search="sourcetype=minecraft_log action=block_broken block_type=QUARTZ_ORE | stats count" %}
143 |
144 | {% searchmanager id="top-miners-search" search="sourcetype=minecraft_log action=block_broken | chart count by player" %}
145 |
146 | {% searchmanager id="mining-rate-search" search="sourcetype=minecraft_log action=block_broken block_type!=TORCH block_type!=CROPS | timechart count by block_type" %}
147 |
148 | {% endblock managers %}
149 |
150 | {% block js %}
151 |
152 |
181 |
182 | {% endblock js %}
--------------------------------------------------------------------------------
/django/minecraft-app/static/minecraft-app/components/calendarheatmap/calendarheatmap.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | // calheat!
4 | // shows a cool looking heatmap based on different time signatures
5 | // requires a timechart search. it dynamically guesses how to set up the
6 | // way to show the time, but you can define any settings you want in the html
7 | // docs: http://kamisama.github.io/cal-heatmap
8 |
9 | // ---settings---
10 |
11 | // domain: (hour, day, week, month, year)
12 | // subDomain: (min, x_min, hour, x_hour, day, x_day, week, x_week, month, x_month)
13 | // -- x_ variants are used to rotate the reading order to left to right, then top to bottom.
14 | // start: set to 'current' for current time or 'earliest' for your earliest data point
15 |
16 | // TODO:
17 | // add a setting for each option at http://kamisama.github.io/cal-heatmap/#options
18 | // rather than using the JS method in the HTML like i'm doing now.
19 |
20 |
21 |
22 | // the data is expected in this format after formatData (epoch time: event count):
23 | // {
24 | // "timestamps":[
25 | // {
26 | // "1378225500":"8",
27 | // "1378225560":"8",
28 | // "1378225620":"8",
29 | // },
30 | // {
31 | // "1378230300":"4",
32 | // "1378230360":"4",
33 | // "1378230660":"2"
34 | // },
35 | // {
36 | // "1378225500":"7",
37 | // "1378225560":"7",
38 | // },
39 | // {
40 | // "1378225500":"6",
41 | // "1378225560":"6",
42 | // "1378225620":"7",
43 | // },
44 | // {
45 | // "1378225500":"41",
46 | // "1378225560":"41",
47 | // },
48 | // {
49 | // "1378225500":"22",
50 | // "1378225560":"22",
51 | // }
52 | // ],
53 |
54 | // -- we add this part onto the actual data --
55 |
56 | // "start":"2013-09-03T16:25:00.000Z",
57 | // "domain":"hour",
58 | // "subDomain":"min"
59 | // }
60 |
61 | define(function(require, exports, module) {
62 |
63 | var _ = require('underscore');
64 | var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview");
65 | var d3 = require("../d3/d3");
66 | var CalHeatMap = require("./contrib/cal-heatmap");
67 |
68 | require("css!./calendarheatmap.css");
69 |
70 | var CalendarHeatMap = SimpleSplunkView.extend({
71 | moduleId: module.id,
72 |
73 | className: "splunk-toolkit-cal-heatmap",
74 |
75 | options: {
76 | managerid: "search1", // your MANAGER ID
77 | data: "preview", // Results type
78 | domain: 'hour', // the largest unit it will differentiate by in squares
79 | subDomain: 'min', // the smaller unit the calheat goes off of
80 | formatLabel: _.identity,
81 | uID: null,
82 | options: {} // the default for custom heatmap options.
83 | },
84 |
85 | output_mode: "json_rows",
86 |
87 | initialize: function() {
88 |
89 | SimpleSplunkView.prototype.initialize.apply(this, arguments);
90 |
91 | this.settings.enablePush("value");
92 |
93 | // whenever domain or subDomain are changed, we will re-render.
94 | this.settings.on("change:domain", this.onDomainChange, this);
95 | this.settings.on("change:subDomain", this.onDomainChange, this);
96 | var uniqueID=Math.floor(Math.random()*1000001);
97 | this.settings.set("uID", uniqueID);
98 | },
99 |
100 | onDomainChange: function() {
101 |
102 | var dom = this.settings.get('domain');
103 | var sd = this.settings.get('subDomain');
104 |
105 | // Knock off the prefix cause it doesnt matter here
106 | var sdShort = sd.replace("x_", "");
107 |
108 | var validDomains = {
109 | 'min' : ['hour'],
110 | 'hour' : ['day', 'week'],
111 | 'day' : ['week', 'month', 'year'],
112 | 'week' : ['month', 'year'],
113 | 'month' : ['year']
114 | };
115 |
116 | // If the current domain is valid for this subdomain
117 | if (_.contains(validDomains[sdShort], dom)){
118 | this.render();
119 | }
120 | else{
121 | console.log(sd + " is and invalid subDomain for " + dom);
122 | }
123 | },
124 |
125 | createView: function() {
126 | return true;
127 | },
128 |
129 | // making the data look how we want it to for updateView to do its job
130 | // in this case, it looks like this:
131 | // {timestamp1: count, timestamp2: count, ... }
132 | formatData: function(data) {
133 | var rawFields = this.resultsModel.data().fields;
134 | var domain = this.settings.get('domain');
135 | var subDomain = this.settings.get('subDomain');
136 |
137 | var filteredFields = _.filter(rawFields, function(d){return d[0] !== "_" });
138 | var objects = _.map(data, function(row) {
139 | return _.object(rawFields, row);
140 | });
141 |
142 | var series = [];
143 | for(var i = 0; i < filteredFields.length; i++) {
144 | series.push({ name: filteredFields[i], timestamps: {}, min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY });
145 | }
146 |
147 | _.each(objects, function(object) {
148 | // Get the timestamp for this object
149 | var time = new Date(object['_time']);
150 | var timeValue = time.valueOf() / 1000;
151 |
152 | // For each actual value, store it in the timestamp object
153 | _.each(filteredFields, function(field, i) {
154 | var value = object[field];
155 | series[i].timestamps[timeValue] = parseInt(value, 10) || 0;
156 | series[i].min = Math.min(series[i].min, value);
157 | series[i].max = Math.max(series[i].max, value);
158 | });
159 | });
160 |
161 | _.each(series, function(serie) {
162 |
163 | });
164 |
165 | return {
166 | series: series,
167 | domain: domain,
168 | subDomain: subDomain,
169 | start: new Date(objects[0]['_time']),
170 | min: new Date(objects[0]['_time']),
171 | max: new Date(objects[objects.length - 1]['_time']),
172 | };
173 | },
174 |
175 | updateView: function(viz, data) {
176 | userOptions = this.settings.get('options')
177 |
178 | this.$el.html('');
179 |
180 | var that = this;
181 | _.each(data.series, function(series, idx) {
182 | var scale = d3.scale.quantile()
183 | .domain([series.min, series.max])
184 | .range([0,1,2,3,4]);
185 | var legend = _.map(scale.quantiles(), function(x) { return Math.round(x); });
186 |
187 | var $el = $("").appendTo(that.el);
188 | var $title = $("
Heatmap for: " + series.name + "
").appendTo($el);
189 | var $buttons = $("").appendTo($el);
190 | var $prev = $("").appendTo($buttons);
191 | var $next = $("").appendTo($buttons);
192 | var options = _.extend({
193 | itemSelector: $el[0],
194 | previousSelector: $prev[0],
195 | nextSelector: $next[0],
196 | data: series.timestamps,
197 | domain: data.domain,
198 | subDomain: data.subDomain,
199 | start: data.start,
200 | range: 4,
201 | cellSize: 12,
202 | cellPadding: 3,
203 | domainGutter: 10,
204 | highlight: ['now', new Date()],
205 | legend: legend,
206 | legendMargin: [0, 0, 20, 0],
207 | legendCellSize: 14,
208 | minDate: data.min,
209 | maxDate: data.max,
210 | onMinDomainReached: function(hit) {
211 | $prev.attr("disabled", hit ? "disabled" : false);
212 | },
213 | onMaxDomainReached: function(hit) {
214 | $next.attr("disabled", hit ? "disabled" : false);
215 | },
216 | onClick: function(date, value) {
217 | that.trigger('click', { date: date, value: value });
218 | that.settings.set('value', date.valueOf());
219 | },
220 | }, userOptions);
221 |
222 | var cal = new CalHeatMap();
223 | cal.init(options); // create the calendar using either default or user defined options */
224 |
225 | if (idx < data.series.length - 1) {
226 | $("").appendTo($el);
227 | }
228 | });
229 | }
230 | });
231 |
232 | return CalendarHeatMap;
233 | });
--------------------------------------------------------------------------------
/django/minecraft-app/static/minecraft-app/components/sunburst/sunburst.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module) {
2 |
3 | var _ = require('underscore');
4 | var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview");
5 | var nester = require("../underscore-nest/underscore-nest");
6 | var d3 = require("../d3/d3");
7 |
8 | require("css!./sunburst.css");
9 |
10 | window.nester = nester;
11 |
12 | var Sunburst = SimpleSplunkView.extend({
13 | moduleId: module.id,
14 |
15 | className: "splunk-toolkit-sunburst",
16 |
17 | options: {
18 | managerid: null,
19 | data: 'preview',
20 | chartTitle: null,
21 | valueField: null,
22 | categoryFields: null,
23 | formatLabel: _.identity,
24 | formatTooltip: function(d) {
25 | return (d.name || "Total") + ": " + d.value;
26 | }
27 | },
28 |
29 | output_mode: "json_rows",
30 |
31 | initialize: function() {
32 | SimpleSplunkView.prototype.initialize.apply(this, arguments);
33 |
34 | // TODO: enable push
35 | // TODO: wire up changes
36 |
37 | this.settings.on("change:valueField", this.render, this);
38 | this.settings.on("change:categoryFields", this.render, this);
39 |
40 | // Set up resize callback. The first argument is a this
41 | // pointer which gets passed into the callback event
42 | $(window).resize(this, _.debounce(this._handleResize, 20));
43 | },
44 |
45 | _handleResize: function(e){
46 |
47 | // e.data is the this pointer passed to the callback.
48 | // here it refers to this object and we call render()
49 | e.data.render();
50 | },
51 |
52 | createView: function() {
53 | // Here we wet up the initial view layout
54 | var margin = {top: 30, right: 30, bottom: 30, left: 30};
55 | var availableWidth = parseInt(this.settings.get("width") || this.$el.width());
56 | var availableHeight = parseInt(this.settings.get("height") || this.$el.height());
57 |
58 | this.$el.html("");
59 |
60 | var svg = d3.select(this.el)
61 | .append("svg")
62 | .attr("width", availableWidth)
63 | .attr("height", availableHeight)
64 | .attr("pointer-events", "all");
65 |
66 | // The returned object gets passed to updateView as viz
67 | return { container: this.$el, svg: svg, margin: margin};
68 | },
69 |
70 | // making the data look how we want it to for updateView to do its job
71 | formatData: function(data) {
72 | var valueField = this.settings.get('valueField');
73 | var rawFields = this.resultsModel.data().fields;
74 | var fieldList = this.settings.get("categoryFields");
75 | if(fieldList){
76 | fieldList = fieldList.split(/[ ,]+/);
77 | }
78 | else{
79 | fieldList = this.resultsModel.data().fields;
80 | }
81 | var objects = _.map(data, function(row) {
82 | return _.object(rawFields, row);
83 | });
84 | var dataResults = nester.nest(objects, fieldList, function(children) {
85 | var total = 0;
86 | _.each(children, function(child){
87 | var size = child[valueField] || 1;
88 | total += size;
89 | })
90 | return total;
91 | });
92 | dataResults['name'] = this.settings.get("chartTitle") || "";
93 | data = {
94 | 'results': dataResults,
95 | 'fields': fieldList
96 | }
97 | return data;
98 | },
99 |
100 | updateView: function(viz, data) {
101 | var that = this;
102 | var formatLabel = this.settings.get("formatLabel") || _.identity;
103 | var formatTooltip = this.settings.get("formatTooltip") || function(d) { return d.name; };
104 | var containerHeight = this.$el.height();
105 | var containerWidth = this.$el.width();
106 |
107 | // Clear svg
108 | var svg = $(viz.svg[0]);
109 | svg.empty();
110 | svg.height(containerHeight);
111 | svg.width(containerWidth);
112 |
113 | // Add the graph group as a child of the main svg
114 | var graphWidth = containerWidth - viz.margin.left - viz.margin.right
115 | var graphHeight = containerHeight - viz.margin.top - viz.margin.bottom;
116 | var graph = viz.svg
117 | .append("g")
118 | .attr("width", graphWidth)
119 | .attr("height", graphHeight)
120 | .attr("transform", "translate("
121 | + ((graphWidth/2) + viz.margin.left ) + ","
122 | + ((graphHeight/2) + viz.margin.top ) + ")");
123 |
124 | var radius = Math.min(graphWidth, graphHeight) / 2;
125 |
126 | var color = d3.scale.category20c();
127 |
128 | var x = d3.scale.linear()
129 | .range([0, 2 * Math.PI]);
130 |
131 | var y = d3.scale.linear()
132 | .range([0, radius]);
133 |
134 | var partition = d3.layout.partition()
135 | .value(function(d) { return d['value']; });
136 |
137 | var arc = d3.svg.arc()
138 | .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
139 | .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
140 | .innerRadius(function(d) { return Math.max(0, y(d.y)); })
141 | .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
142 |
143 | var root = data.results;
144 |
145 | var g = graph.selectAll("g")
146 | .data(partition.nodes(root))
147 | .enter().append("g");
148 |
149 | var path = g.append("path")
150 | .attr("d", arc)
151 | .style("fill", function(d) {return color((d.children ? d : d.parent).name); })
152 | .on("click", click);
153 |
154 | path.append("title")
155 | .text(formatTooltip);
156 |
157 | var text = g.append("text")
158 | .attr("text-anchor", function(d) {
159 | return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
160 | })
161 | .attr("transform", function(d) {
162 | var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90;
163 | var rotate = angle;
164 | var padding = 5;
165 | return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
166 | })
167 | .attr("dy", ".2em")
168 | .attr("x", 0)
169 | .text(function(d) { return formatLabel(d.name); })
170 | .on("click", click);
171 |
172 | text.append("title")
173 | .text(formatTooltip);
174 |
175 | function click(d) {
176 | // fade out all text elements
177 | text.transition().attr("opacity", 0);
178 |
179 | path.transition()
180 | .duration(750)
181 | .attrTween("d", arcTween(d))
182 | .each("end", function(e, i) {
183 | // check if the animated element's data e lies within the visible angle span given in d
184 | if (e.x >= d.x && e.x < (d.x + d.dx)) {
185 | // get a selection of the associated text element
186 | var arcText = d3.select(this.parentNode).select("text");
187 | // fade in the text element and recalculate positions
188 | arcText.transition().duration(750)
189 | .attr("opacity", 1)
190 | .attr("text-anchor", function(d) {
191 | return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
192 | })
193 | .attr("transform", function(d) {
194 | var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90;
195 | var rotate = angle;
196 | var padding = 5;
197 | return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
198 | })
199 | .attr("dy", ".2em")
200 | .attr("x", 0)
201 | }
202 | });
203 | }
204 |
205 | // Interpolate the scales!
206 | function arcTween(d) {
207 | var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
208 | yd = d3.interpolate(y.domain(), [d.y, 1]),
209 | yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
210 | return function(d, i) {
211 | return i
212 | ? function(t) { return arc(d); }
213 | : function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
214 | };
215 | }
216 |
217 | function computeTextRotation(d) {
218 | return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
219 | }
220 |
221 | }
222 | });
223 | return Sunburst;
224 | });
--------------------------------------------------------------------------------
/django/minecraft-app/static/minecraft-app/components/bubblechart/bubblechart.js:
--------------------------------------------------------------------------------
1 | // Bubble Chart
2 | // this displays information as different 'bubbles,' their unique values represented with
3 | // the size of the bubble.
4 | // supports drilldown clicks
5 |
6 | // available settings:
7 | // - labelField: the field to use as the label on each bubble
8 | // - valueField: the field to use as the value of each bubble (also dictates size)
9 | // - categoryField: the field to use for grouping similar data (usually the same field as labelField)
10 |
11 | // ---expected data format---
12 | // a splunk search like this: source=foo | stats count by artist_name, track_name
13 |
14 | define(function(require, exports, module) {
15 |
16 | var _ = require('underscore');
17 | var d3 = require("../d3/d3");
18 | var SimpleSplunkView = require("splunkjs/mvc/simplesplunkview");
19 |
20 | require("css!./bubblechart.css");
21 |
22 | var BubbleChart = SimpleSplunkView.extend({
23 | moduleId: module.id,
24 |
25 | className: "splunk-toolkit-bubble-chart",
26 |
27 | options: {
28 | managerid: null,
29 | data: "preview",
30 | labelField: null,
31 | labelField: null,
32 | valueField: 'count',
33 | categoryField: null,
34 | },
35 |
36 | output_mode: "json",
37 |
38 | initialize: function() {
39 |
40 | SimpleSplunkView.prototype.initialize.apply(this, arguments);
41 |
42 | this.settings.enablePush("value");
43 |
44 | // in the case that any options are changed, it will dynamically update
45 | // without having to refresh. copy the following line for whichever field
46 | // you'd like dynamic updating on
47 | this.settings.on("change:valueField", this.render, this);
48 | this.settings.on("change:labelField", this.render, this);
49 | this.settings.on("change:categoryField", this.render, this);
50 |
51 | // NOTE: nameField is depricated. Use labelField
52 | if (this.settings.get("nameField")) {
53 | this.settings.set("labelField", this.settings.get("nameField"));
54 | }
55 | this.settings.on("change:nameField", this._onNameFieldChange, this);
56 |
57 | // Set up resize callback. The first argument is a this
58 | // pointer which gets passed into the callback event
59 | $(window).resize(this, _.debounce(this._handleResize, 20));
60 | },
61 |
62 | _handleResize: function(e){
63 |
64 | // e.data is the this pointer passed to the callback.
65 | // here it refers to this object and we call render()
66 | e.data.render();
67 | },
68 |
69 | // This is for compatibility. nameField is depricated and labelField should be used
70 | _onNameFieldChange: function(e){
71 | this.settings.set("labelField", this.settings.get("nameField"));
72 | this.render();
73 | },
74 |
75 | createView: function() {
76 |
77 | // Here we wet up the initial view layout
78 | var margin = {top: 0, right: 0, bottom: 0, left: 0};
79 | var availableWidth = parseInt(this.settings.get("width") || this.$el.width());
80 | var availableHeight = parseInt(this.settings.get("height") || this.$el.height());
81 |
82 | this.$el.html("");
83 |
84 | var svg = d3.select(this.el)
85 | .append("svg")
86 | .attr("width", availableWidth)
87 | .attr("height", availableHeight)
88 | .attr("pointer-events", "all");
89 |
90 | var tooltip = d3.select(this.el).append("div")
91 | .attr("class", "bubble-chart-tooltip");
92 |
93 | // The returned object gets passed to updateView as viz
94 | return { container: this.$el, svg: svg, margin: margin, tooltip: tooltip};
95 | },
96 |
97 | // making the data look how we want it to for updateView to do its job
98 | formatData: function(data) {
99 | // getting settings
100 | var labelField = this.settings.get('labelField');
101 | var valueField = this.settings.get('valueField');
102 | var categoryField = this.settings.get('categoryField');
103 | var collection = data;
104 | var bubblechart = { 'name': labelField+"s", 'children': [ ] }; // how we want it to look
105 |
106 | // making the children formatted array
107 | for (var i=0; i < collection.length; i++) {
108 | var Idx = -1;
109 | $.each(bubblechart.children, function(idx, el) {
110 | if (el.name == collection[i][categoryField]) {
111 | Idx = idx;
112 | }
113 | });
114 | if (Idx == -1) {
115 | bubblechart.children.push({ 'name': collection[i][categoryField], children: [ ] });
116 | Idx = bubblechart.children.length - 1;
117 | }
118 |
119 | bubblechart.children[Idx].children.push({ 'name': collection[i][labelField], 'size': collection[i][valueField] || 1 });
120 | }
121 | return bubblechart; // this is passed into updateView as 'data'
122 | },
123 |
124 | updateView: function(viz, data) {
125 | var that = this;
126 |
127 | // Clear svg
128 | var svg = $(viz.svg[0]);
129 | svg.empty();
130 |
131 | var tooltip = viz.tooltip;
132 |
133 | // Add the graph group as a child of the main svg
134 | var graph = viz.svg
135 | .append("g")
136 | .attr("class", "bubble")
137 | .attr("transform", "translate(" + viz.margin.left + "," + viz.margin.top + ")");
138 |
139 | // Set format and color
140 | var format = d3.format(",d");
141 | var color = d3.scale.category20c();
142 |
143 | // We have two phases in layout. We tell the
144 | // d3 lout how much room it has, then set
145 | // the sizes of it's containers to match
146 | // the size it returns.
147 | var containerHeight = this.$el.height();
148 | var containerWidth = this.$el.width();
149 | var diameter = Math.min(containerWidth, containerHeight);
150 |
151 | // Tell the layout to layout
152 | var bubble = d3.layout.pack()
153 | .sort(null)
154 | .size([diameter, diameter])
155 | .padding(1.5);
156 |
157 | // Set containers' sizes to match actual layout
158 | var width = bubble.size()[0];
159 | var height = bubble.size()[1];
160 | graph.attr("width", width)
161 | .attr("height", height);
162 | svg.height(height);
163 | svg.width(width);
164 |
165 | var node = graph.selectAll(".node")
166 | .data(bubble.nodes(classes(data))
167 | .filter(function(d) { return !d.children; }))
168 | .enter().append("g")
169 | .attr("class", "node")
170 | .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
171 |
172 | // NOTE: this is taken out because we have a custom tooltip.
173 | // It may need to be put back for accessibility
174 | // node.append("title")
175 | // .text(function(d) { return d.className + ": " + format(d.value); });
176 |
177 | node.append("circle")
178 | .attr("r", function(d) { return d.r; })
179 | .style("fill", function(d) { return color(d.packageName); });
180 |
181 | node.append("text")
182 | .attr("dy", ".3em")
183 | .style("text-anchor", "middle")
184 | // ensure the text is truncated if the bubble is tiny
185 | .text(function(d) { return (d.className + " " + format(d.value)).substring(0, d.r / 3); });
186 |
187 | // Re-flatten the child array
188 | function classes(data) {
189 | var classes = [];
190 | function recurse(name, node) {
191 | if (node.children)
192 | node.children.forEach(function(child) {
193 | recurse(node.name, child);
194 | });
195 | else
196 | classes.push({packageName: name || "", className: node.name || "", value: node.size});
197 | }
198 |
199 | recurse(null, data);
200 | return {children: classes};
201 | }
202 |
203 | // Tooltips
204 | function doMouseEnter(d){
205 | var text;
206 | if(d.className === undefined || d.className === ""){
207 | text = "Event: " + d.value;
208 | } else {
209 | text = d.className+": " + d.value;
210 | }
211 | tooltip
212 | .text(text)
213 | .style("opacity", function(){
214 | if(d.value !== undefined) { return 1; }
215 | return 0;
216 | })
217 | .style("left", (d3.mouse(that.el)[0]) + "px")
218 | .style("top", (d3.mouse(that.el)[1]) + "px");
219 | }
220 |
221 | // More tooltips
222 | function doMouseOut(d){
223 | tooltip.style("opacity", 1e-6);
224 | }
225 |
226 | node.on("mouseover", doMouseEnter);
227 | node.on("mouseout", doMouseOut);
228 |
229 | // Drilldown clickings. edit this in order to change the search token that
230 | // is set to 'value' (a token in bubbles django), this will change the drilldown
231 | // search.
232 | node.on('click', function(e) {
233 | var clickEvent = {
234 | name: e.className,
235 | category: e.packageName,
236 | value: e.value
237 | };
238 | that.settings.set("value", e.className);
239 | that.trigger("click", clickEvent);
240 | });
241 | }
242 | });
243 | return BubbleChart;
244 | });
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "[]" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright [yyyy] [name of copyright owner]
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------