Skip to content
Snippets Groups Projects
Commit 5301ebe7 authored by Allen Gilliland's avatar Allen Gilliland
Browse files

Merge pull request #2083 from metabase/xkcd-chart

XKCD Chart Easter Egg
parents 2de7af2a b43e4185
No related branches found
No related tags found
No related merge requests found
:local(.XKCDChart) {
font-family: "Humor Sans", sans-serif;
font-size: 16px;
color: #333;
text-align: center;
}
:local(.XKCDChart) text.title {
font-size: 20px;
}
:local(.XKCDChart) path {
fill: none;
stroke-width: 2.5px;
stroke-linecap: round;
stroke-linejoin: round;
}
:local(.XKCDChart) path.axis {
stroke: black;
}
:local(.XKCDChart) path.bgline {
stroke: white;
stroke-width: 6px;
}
import React, { Component, PropTypes } from "react";
import ReactDOM from "react-dom";
import styles from "./XKCDChart.css";
import { MinColumnsError, MinRowsError } from "metabase/visualizations/lib/errors";
import {
getFriendlyName,
getAvailableCanvasWidth,
getAvailableCanvasHeight
} from "metabase/visualizations/lib/utils";
import { dimensionIsTimeseries } from "./lib/timeseries";
import xkcdplot from "xkcdplot";
import "xkcdplot/humor-sans";
import cx from "classnames";
export default class XKCDChart extends Component {
static displayName = "XKCD"
static identifier = "xkcd";
static iconName = "pinmap";
static noHeader = true;
static isSensible(cols, rows) {
return rows.length > 1 && cols.length > 1;
}
static checkRenderable(cols, rows) {
if (cols.length < 2) { throw new MinColumnsError(2, cols.length); }
if (rows.length < 1) { throw new MinRowsError(1, rows.length); }
}
componentDidMount() {
this.componentDidUpdate();
}
componentDidUpdate() {
let { series } = this.props;
let parent = ReactDOM.findDOMNode(this);
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
let margin = 50;
// Build the plot.
var plot = xkcdplot()
.width(Math.min(800, getAvailableCanvasWidth(parent) - margin * 2))
.height(Math.min(600, getAvailableCanvasHeight(parent) - margin * 2))
.margin(margin)
.xlabel(getFriendlyName(series[0].data.cols[0]))
.ylabel(getFriendlyName(series[0].data.cols[1]));
if (series[0].card.name) {
plot.title(series[0].card.name);
}
plot(parent);
let colors = [undefined, "red", "grey", "green", "yellow"];
let isTimeseries = dimensionIsTimeseries(series[0].data);
series.map((s, index) => {
let data = s.data.rows.map(row => ({
x: isTimeseries ? new Date(row[0]).getTime() : row[0],
y: row[1]
}));
plot.plot(data, { stroke: colors[index % colors.length] });
})
plot.draw();
if (series[0].card.id) {
let title = parent.querySelector(".title");
title.style = "cursor: pointer;";
title.addEventListener("click", () => window.location = "/card/" + series[0].card.id);
}
}
render() {
return (
<div className={cx(this.props.className, styles.XKCDChart)} />
);
}
}
......@@ -35,4 +35,10 @@ registerVisualization(USStateMap);
registerVisualization(WorldMap);
registerVisualization(PinMap);
import { enableVisualizationEasterEgg } from "./lib/utils";
import XKCDChart from "./XKCDChart.jsx";
import LineAreaBarChart from "./components/LineAreaBarChart.jsx";
enableVisualizationEasterEgg("XKCD", LineAreaBarChart, XKCDChart);
export default visualizations;
import React from "react";
import _ from "underscore";
import d3 from "d3";
......@@ -127,3 +128,41 @@ export function colorShade(hex, shade = 0) {
Math.round(min + (max - min) * shade * (c / 255)).toString(16)
).join("");
}
export function enableVisualizationEasterEgg(code, OriginalVisualization, EasterEggVisualization) {
if (!code) {
code = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
} else if (typeof code === "string") {
code = code.split("").map(c => c.charCodeAt(0));
}
wrapMethod(OriginalVisualization.prototype, "componentWillMount", function easterEgg() {
let keypresses = [];
let enabled = false;
let render_original = this.render;
let render_egg = function() {
return <EasterEggVisualization {...this.props} />;
};
this._keyListener = (e) => {
keypresses = keypresses.concat(e.keyCode).slice(-code.length);
if (code.reduce((ok, value, index) => ok && value === keypresses[index], true)) {
enabled = !enabled;
this.render = enabled ? render_egg : render_original;
this.forceUpdate();
}
}
window.addEventListener("keyup", this._keyListener, false);
});
wrapMethod(OriginalVisualization.prototype, "componentWillUnmount", function cleanupEasterEgg() {
window.removeEventListener("keyup", this._keyListener, false);
});
}
function wrapMethod(object, name, method) {
let method_original = object[name];
object[name] = function() {
method.apply(this, arguments);
if (typeof method_original === "function") {
return method_original.apply(this, arguments);
}
}
}
This diff is collapsed.
......@@ -51,6 +51,7 @@
"screenfull": "^3.0.0",
"tether": "^1.2.0",
"underscore": "^1.8.3",
"xkcdplot": "^1.1.0",
"z-index": "0.0.1"
},
"devDependencies": {
......@@ -66,6 +67,7 @@
"eslint-loader": "^1.3.0",
"eslint-plugin-react": "^4.1.0",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.8.5",
"glob": "^5.0.15",
"html-webpack-plugin": "^2.9.0",
"istanbul-instrumenter-loader": "^0.2.0",
......
......@@ -151,8 +151,11 @@
:style-src ["'unsafe-inline'"
"'self'"
"fonts.googleapis.com"]
:font-src ["fonts.gstatic.com"
"themes.googleusercontent.com"]
:font-src ["'self'"
"fonts.gstatic.com"
"themes.googleusercontent.com"
(when config/is-dev?
"localhost:8080")]
:img-src ["*"
"self data:"]
:connect-src ["'self'"
......
......@@ -82,7 +82,7 @@ var config = module.exports = {
path: BUILD_PATH + '/app/dist',
// NOTE: the filename on disk won't include "?[chunkhash]" but the URL in index.html generated by HtmlWebpackPlugin will:
filename: '[name].bundle.js?[hash]',
publicPath: '/app/dist'
publicPath: '/app/dist/'
},
module: {
......@@ -98,6 +98,10 @@ var config = module.exports = {
exclude: /node_modules|\.spec\.js/,
loader: 'eslint'
},
{
test: /\.(eot|woff2?|ttf|svg)$/,
loader: "file-loader"
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader?" + JSON.stringify(CSS_CONFIG) + "!postcss-loader")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment