diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:26:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:26:01 +0000 |
commit | ef03469fec14f1f0358b690934fc173d744f4e7d (patch) | |
tree | 8be439d7b2f1d7c8283b745919b9e66481a950e7 /debian/missing-sources/epoch | |
parent | Adding upstream version 5.6.0. (diff) | |
download | knot-resolver-ef03469fec14f1f0358b690934fc173d744f4e7d.tar.xz knot-resolver-ef03469fec14f1f0358b690934fc173d744f4e7d.zip |
Adding debian version 5.6.0-1.debian/5.6.0-1debian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
75 files changed, 17525 insertions, 0 deletions
diff --git a/debian/missing-sources/epoch.css b/debian/missing-sources/epoch.css new file mode 100644 index 0000000..84d97e3 --- /dev/null +++ b/debian/missing-sources/epoch.css @@ -0,0 +1,1494 @@ +/* Epoch Master SCSS Includes the core styles and all the themes to produce the complete epoch css file. By Ryan Sandor Richards Copyright 2013 Fastly, Inc. */ +/* Core Epoch Styles */ +/** Generates the styles needed to define a fill color for a given class name. @param $name Name of the class to use (sans the leading .) @param $color Fill color to associate with the class name. */ +/** Produces categorical color classes for plots (excluding heatmaps). @param $list List of colors to use for each category. @param $entries Size of the input list. */ +/** Produces categorical colors for heatmaps. @param $list List of colors to use for categories. @param $entries Size of the input list. */ +.epoch .axis path, .epoch .axis line { shape-rendering: crispEdges; } + +.epoch .axis.canvas .tick line { shape-rendering: geometricPrecision; } + +/* Canvas Styles Reference Container The reference container is an SVG that is automatically created when Epoch is loaded. It is used by the canvas based plots to obtain color information from the page styles by creating reference elements and then reading their computed styles. Note: don't mess with this ;) */ +div#_canvas_css_reference { width: 0; height: 0; position: absolute; top: -1000px; left: -1000px; } + +div#_canvas_css_reference svg { position: absolute; width: 0; height: 0; top: -1000px; left: -1000px; } + +/* theme/_default.scss - Default Color Theme Categorical Colors Adapted from d3: https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors */ +.epoch { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 12pt; } + +.epoch .axis path, .epoch .axis line { fill: transparent; stroke: #000; } + +.epoch .axis .tick text { font-size: 9pt; } + +.epoch .line { fill: transparent; stroke-width: 2px; } + +.epoch.sparklines .line { stroke-width: 1px; } + +.epoch .area { stroke: transparent; } + +.epoch .arc.pie { stroke: #fff; stroke-width: 1.5px; } + +.epoch .arc.pie text { stroke: transparent; fill: white; font-size: 9pt; } + +.epoch .gauge-labels .value { text-anchor: middle; font-size: 140%; fill: #666; } + +.epoch.gauge-tiny { width: 120px; height: 90px; } + +.epoch.gauge-tiny .gauge-labels .value { font-size: 80%; } + +.epoch.gauge-tiny .gauge .arc.outer { stroke-width: 2px; } + +.epoch.gauge-small { width: 180px; height: 135px; } + +.epoch.gauge-small .gauge-labels .value { font-size: 120%; } + +.epoch.gauge-small .gauge .arc.outer { stroke-width: 3px; } + +.epoch.gauge-medium { width: 240px; height: 180px; } + +.epoch.gauge-medium .gauge .arc.outer { stroke-width: 3px; } + +.epoch.gauge-large { width: 320px; height: 240px; } + +.epoch.gauge-large .gauge-labels .value { font-size: 180%; } + +.epoch .gauge .arc.outer { stroke-width: 4px; stroke: #666; } + +.epoch .gauge .arc.inner { stroke-width: 1px; stroke: #555; } + +.epoch .gauge .tick { stroke-width: 1px; stroke: #555; } + +.epoch .gauge .needle { fill: orange; } + +.epoch .gauge .needle-base { fill: #666; } + +.epoch div.ref.category1, .epoch.category10 div.ref.category1 { background-color: #1f77b4; } + +.epoch .category1 .line, .epoch.category10 .category1 .line { stroke: #1f77b4; } + +.epoch .category1 .area, .epoch .category1 .dot, .epoch.category10 .category1 .area, .epoch.category10 .category1 .dot { fill: #1f77b4; stroke: transparent; } + +.epoch .arc.category1 path, .epoch.category10 .arc.category1 path { fill: #1f77b4; } + +.epoch .bar.category1, .epoch.category10 .bar.category1 { fill: #1f77b4; } + +.epoch div.ref.category2, .epoch.category10 div.ref.category2 { background-color: #ff7f0e; } + +.epoch .category2 .line, .epoch.category10 .category2 .line { stroke: #ff7f0e; } + +.epoch .category2 .area, .epoch .category2 .dot, .epoch.category10 .category2 .area, .epoch.category10 .category2 .dot { fill: #ff7f0e; stroke: transparent; } + +.epoch .arc.category2 path, .epoch.category10 .arc.category2 path { fill: #ff7f0e; } + +.epoch .bar.category2, .epoch.category10 .bar.category2 { fill: #ff7f0e; } + +.epoch div.ref.category3, .epoch.category10 div.ref.category3 { background-color: #2ca02c; } + +.epoch .category3 .line, .epoch.category10 .category3 .line { stroke: #2ca02c; } + +.epoch .category3 .area, .epoch .category3 .dot, .epoch.category10 .category3 .area, .epoch.category10 .category3 .dot { fill: #2ca02c; stroke: transparent; } + +.epoch .arc.category3 path, .epoch.category10 .arc.category3 path { fill: #2ca02c; } + +.epoch .bar.category3, .epoch.category10 .bar.category3 { fill: #2ca02c; } + +.epoch div.ref.category4, .epoch.category10 div.ref.category4 { background-color: #d62728; } + +.epoch .category4 .line, .epoch.category10 .category4 .line { stroke: #d62728; } + +.epoch .category4 .area, .epoch .category4 .dot, .epoch.category10 .category4 .area, .epoch.category10 .category4 .dot { fill: #d62728; stroke: transparent; } + +.epoch .arc.category4 path, .epoch.category10 .arc.category4 path { fill: #d62728; } + +.epoch .bar.category4, .epoch.category10 .bar.category4 { fill: #d62728; } + +.epoch div.ref.category5, .epoch.category10 div.ref.category5 { background-color: #9467bd; } + +.epoch .category5 .line, .epoch.category10 .category5 .line { stroke: #9467bd; } + +.epoch .category5 .area, .epoch .category5 .dot, .epoch.category10 .category5 .area, .epoch.category10 .category5 .dot { fill: #9467bd; stroke: transparent; } + +.epoch .arc.category5 path, .epoch.category10 .arc.category5 path { fill: #9467bd; } + +.epoch .bar.category5, .epoch.category10 .bar.category5 { fill: #9467bd; } + +.epoch div.ref.category6, .epoch.category10 div.ref.category6 { background-color: #8c564b; } + +.epoch .category6 .line, .epoch.category10 .category6 .line { stroke: #8c564b; } + +.epoch .category6 .area, .epoch .category6 .dot, .epoch.category10 .category6 .area, .epoch.category10 .category6 .dot { fill: #8c564b; stroke: transparent; } + +.epoch .arc.category6 path, .epoch.category10 .arc.category6 path { fill: #8c564b; } + +.epoch .bar.category6, .epoch.category10 .bar.category6 { fill: #8c564b; } + +.epoch div.ref.category7, .epoch.category10 div.ref.category7 { background-color: #e377c2; } + +.epoch .category7 .line, .epoch.category10 .category7 .line { stroke: #e377c2; } + +.epoch .category7 .area, .epoch .category7 .dot, .epoch.category10 .category7 .area, .epoch.category10 .category7 .dot { fill: #e377c2; stroke: transparent; } + +.epoch .arc.category7 path, .epoch.category10 .arc.category7 path { fill: #e377c2; } + +.epoch .bar.category7, .epoch.category10 .bar.category7 { fill: #e377c2; } + +.epoch div.ref.category8, .epoch.category10 div.ref.category8 { background-color: #7f7f7f; } + +.epoch .category8 .line, .epoch.category10 .category8 .line { stroke: #7f7f7f; } + +.epoch .category8 .area, .epoch .category8 .dot, .epoch.category10 .category8 .area, .epoch.category10 .category8 .dot { fill: #7f7f7f; stroke: transparent; } + +.epoch .arc.category8 path, .epoch.category10 .arc.category8 path { fill: #7f7f7f; } + +.epoch .bar.category8, .epoch.category10 .bar.category8 { fill: #7f7f7f; } + +.epoch div.ref.category9, .epoch.category10 div.ref.category9 { background-color: #bcbd22; } + +.epoch .category9 .line, .epoch.category10 .category9 .line { stroke: #bcbd22; } + +.epoch .category9 .area, .epoch .category9 .dot, .epoch.category10 .category9 .area, .epoch.category10 .category9 .dot { fill: #bcbd22; stroke: transparent; } + +.epoch .arc.category9 path, .epoch.category10 .arc.category9 path { fill: #bcbd22; } + +.epoch .bar.category9, .epoch.category10 .bar.category9 { fill: #bcbd22; } + +.epoch div.ref.category10, .epoch.category10 div.ref.category10 { background-color: #17becf; } + +.epoch .category10 .line, .epoch.category10 .category10 .line { stroke: #17becf; } + +.epoch .category10 .area, .epoch .category10 .dot, .epoch.category10 .category10 .area, .epoch.category10 .category10 .dot { fill: #17becf; stroke: transparent; } + +.epoch .arc.category10 path, .epoch.category10 .arc.category10 path { fill: #17becf; } + +.epoch .bar.category10, .epoch.category10 .bar.category10 { fill: #17becf; } + +.epoch.category20 div.ref.category1 { background-color: #1f77b4; } + +.epoch.category20 .category1 .line { stroke: #1f77b4; } + +.epoch.category20 .category1 .area, .epoch.category20 .category1 .dot { fill: #1f77b4; stroke: transparent; } + +.epoch.category20 .arc.category1 path { fill: #1f77b4; } + +.epoch.category20 .bar.category1 { fill: #1f77b4; } + +.epoch.category20 div.ref.category2 { background-color: #aec7e8; } + +.epoch.category20 .category2 .line { stroke: #aec7e8; } + +.epoch.category20 .category2 .area, .epoch.category20 .category2 .dot { fill: #aec7e8; stroke: transparent; } + +.epoch.category20 .arc.category2 path { fill: #aec7e8; } + +.epoch.category20 .bar.category2 { fill: #aec7e8; } + +.epoch.category20 div.ref.category3 { background-color: #ff7f0e; } + +.epoch.category20 .category3 .line { stroke: #ff7f0e; } + +.epoch.category20 .category3 .area, .epoch.category20 .category3 .dot { fill: #ff7f0e; stroke: transparent; } + +.epoch.category20 .arc.category3 path { fill: #ff7f0e; } + +.epoch.category20 .bar.category3 { fill: #ff7f0e; } + +.epoch.category20 div.ref.category4 { background-color: #ffbb78; } + +.epoch.category20 .category4 .line { stroke: #ffbb78; } + +.epoch.category20 .category4 .area, .epoch.category20 .category4 .dot { fill: #ffbb78; stroke: transparent; } + +.epoch.category20 .arc.category4 path { fill: #ffbb78; } + +.epoch.category20 .bar.category4 { fill: #ffbb78; } + +.epoch.category20 div.ref.category5 { background-color: #2ca02c; } + +.epoch.category20 .category5 .line { stroke: #2ca02c; } + +.epoch.category20 .category5 .area, .epoch.category20 .category5 .dot { fill: #2ca02c; stroke: transparent; } + +.epoch.category20 .arc.category5 path { fill: #2ca02c; } + +.epoch.category20 .bar.category5 { fill: #2ca02c; } + +.epoch.category20 div.ref.category6 { background-color: #98df8a; } + +.epoch.category20 .category6 .line { stroke: #98df8a; } + +.epoch.category20 .category6 .area, .epoch.category20 .category6 .dot { fill: #98df8a; stroke: transparent; } + +.epoch.category20 .arc.category6 path { fill: #98df8a; } + +.epoch.category20 .bar.category6 { fill: #98df8a; } + +.epoch.category20 div.ref.category7 { background-color: #d62728; } + +.epoch.category20 .category7 .line { stroke: #d62728; } + +.epoch.category20 .category7 .area, .epoch.category20 .category7 .dot { fill: #d62728; stroke: transparent; } + +.epoch.category20 .arc.category7 path { fill: #d62728; } + +.epoch.category20 .bar.category7 { fill: #d62728; } + +.epoch.category20 div.ref.category8 { background-color: #ff9896; } + +.epoch.category20 .category8 .line { stroke: #ff9896; } + +.epoch.category20 .category8 .area, .epoch.category20 .category8 .dot { fill: #ff9896; stroke: transparent; } + +.epoch.category20 .arc.category8 path { fill: #ff9896; } + +.epoch.category20 .bar.category8 { fill: #ff9896; } + +.epoch.category20 div.ref.category9 { background-color: #9467bd; } + +.epoch.category20 .category9 .line { stroke: #9467bd; } + +.epoch.category20 .category9 .area, .epoch.category20 .category9 .dot { fill: #9467bd; stroke: transparent; } + +.epoch.category20 .arc.category9 path { fill: #9467bd; } + +.epoch.category20 .bar.category9 { fill: #9467bd; } + +.epoch.category20 div.ref.category10 { background-color: #c5b0d5; } + +.epoch.category20 .category10 .line { stroke: #c5b0d5; } + +.epoch.category20 .category10 .area, .epoch.category20 .category10 .dot { fill: #c5b0d5; stroke: transparent; } + +.epoch.category20 .arc.category10 path { fill: #c5b0d5; } + +.epoch.category20 .bar.category10 { fill: #c5b0d5; } + +.epoch.category20 div.ref.category11 { background-color: #8c564b; } + +.epoch.category20 .category11 .line { stroke: #8c564b; } + +.epoch.category20 .category11 .area, .epoch.category20 .category11 .dot { fill: #8c564b; stroke: transparent; } + +.epoch.category20 .arc.category11 path { fill: #8c564b; } + +.epoch.category20 .bar.category11 { fill: #8c564b; } + +.epoch.category20 div.ref.category12 { background-color: #c49c94; } + +.epoch.category20 .category12 .line { stroke: #c49c94; } + +.epoch.category20 .category12 .area, .epoch.category20 .category12 .dot { fill: #c49c94; stroke: transparent; } + +.epoch.category20 .arc.category12 path { fill: #c49c94; } + +.epoch.category20 .bar.category12 { fill: #c49c94; } + +.epoch.category20 div.ref.category13 { background-color: #e377c2; } + +.epoch.category20 .category13 .line { stroke: #e377c2; } + +.epoch.category20 .category13 .area, .epoch.category20 .category13 .dot { fill: #e377c2; stroke: transparent; } + +.epoch.category20 .arc.category13 path { fill: #e377c2; } + +.epoch.category20 .bar.category13 { fill: #e377c2; } + +.epoch.category20 div.ref.category14 { background-color: #f7b6d2; } + +.epoch.category20 .category14 .line { stroke: #f7b6d2; } + +.epoch.category20 .category14 .area, .epoch.category20 .category14 .dot { fill: #f7b6d2; stroke: transparent; } + +.epoch.category20 .arc.category14 path { fill: #f7b6d2; } + +.epoch.category20 .bar.category14 { fill: #f7b6d2; } + +.epoch.category20 div.ref.category15 { background-color: #7f7f7f; } + +.epoch.category20 .category15 .line { stroke: #7f7f7f; } + +.epoch.category20 .category15 .area, .epoch.category20 .category15 .dot { fill: #7f7f7f; stroke: transparent; } + +.epoch.category20 .arc.category15 path { fill: #7f7f7f; } + +.epoch.category20 .bar.category15 { fill: #7f7f7f; } + +.epoch.category20 div.ref.category16 { background-color: #c7c7c7; } + +.epoch.category20 .category16 .line { stroke: #c7c7c7; } + +.epoch.category20 .category16 .area, .epoch.category20 .category16 .dot { fill: #c7c7c7; stroke: transparent; } + +.epoch.category20 .arc.category16 path { fill: #c7c7c7; } + +.epoch.category20 .bar.category16 { fill: #c7c7c7; } + +.epoch.category20 div.ref.category17 { background-color: #bcbd22; } + +.epoch.category20 .category17 .line { stroke: #bcbd22; } + +.epoch.category20 .category17 .area, .epoch.category20 .category17 .dot { fill: #bcbd22; stroke: transparent; } + +.epoch.category20 .arc.category17 path { fill: #bcbd22; } + +.epoch.category20 .bar.category17 { fill: #bcbd22; } + +.epoch.category20 div.ref.category18 { background-color: #dbdb8d; } + +.epoch.category20 .category18 .line { stroke: #dbdb8d; } + +.epoch.category20 .category18 .area, .epoch.category20 .category18 .dot { fill: #dbdb8d; stroke: transparent; } + +.epoch.category20 .arc.category18 path { fill: #dbdb8d; } + +.epoch.category20 .bar.category18 { fill: #dbdb8d; } + +.epoch.category20 div.ref.category19 { background-color: #17becf; } + +.epoch.category20 .category19 .line { stroke: #17becf; } + +.epoch.category20 .category19 .area, .epoch.category20 .category19 .dot { fill: #17becf; stroke: transparent; } + +.epoch.category20 .arc.category19 path { fill: #17becf; } + +.epoch.category20 .bar.category19 { fill: #17becf; } + +.epoch.category20 div.ref.category20 { background-color: #9edae5; } + +.epoch.category20 .category20 .line { stroke: #9edae5; } + +.epoch.category20 .category20 .area, .epoch.category20 .category20 .dot { fill: #9edae5; stroke: transparent; } + +.epoch.category20 .arc.category20 path { fill: #9edae5; } + +.epoch.category20 .bar.category20 { fill: #9edae5; } + +.epoch.category20b div.ref.category1 { background-color: #393b79; } + +.epoch.category20b .category1 .line { stroke: #393b79; } + +.epoch.category20b .category1 .area, .epoch.category20b .category1 .dot { fill: #393b79; stroke: transparent; } + +.epoch.category20b .arc.category1 path { fill: #393b79; } + +.epoch.category20b .bar.category1 { fill: #393b79; } + +.epoch.category20b div.ref.category2 { background-color: #5254a3; } + +.epoch.category20b .category2 .line { stroke: #5254a3; } + +.epoch.category20b .category2 .area, .epoch.category20b .category2 .dot { fill: #5254a3; stroke: transparent; } + +.epoch.category20b .arc.category2 path { fill: #5254a3; } + +.epoch.category20b .bar.category2 { fill: #5254a3; } + +.epoch.category20b div.ref.category3 { background-color: #6b6ecf; } + +.epoch.category20b .category3 .line { stroke: #6b6ecf; } + +.epoch.category20b .category3 .area, .epoch.category20b .category3 .dot { fill: #6b6ecf; stroke: transparent; } + +.epoch.category20b .arc.category3 path { fill: #6b6ecf; } + +.epoch.category20b .bar.category3 { fill: #6b6ecf; } + +.epoch.category20b div.ref.category4 { background-color: #9c9ede; } + +.epoch.category20b .category4 .line { stroke: #9c9ede; } + +.epoch.category20b .category4 .area, .epoch.category20b .category4 .dot { fill: #9c9ede; stroke: transparent; } + +.epoch.category20b .arc.category4 path { fill: #9c9ede; } + +.epoch.category20b .bar.category4 { fill: #9c9ede; } + +.epoch.category20b div.ref.category5 { background-color: #637939; } + +.epoch.category20b .category5 .line { stroke: #637939; } + +.epoch.category20b .category5 .area, .epoch.category20b .category5 .dot { fill: #637939; stroke: transparent; } + +.epoch.category20b .arc.category5 path { fill: #637939; } + +.epoch.category20b .bar.category5 { fill: #637939; } + +.epoch.category20b div.ref.category6 { background-color: #8ca252; } + +.epoch.category20b .category6 .line { stroke: #8ca252; } + +.epoch.category20b .category6 .area, .epoch.category20b .category6 .dot { fill: #8ca252; stroke: transparent; } + +.epoch.category20b .arc.category6 path { fill: #8ca252; } + +.epoch.category20b .bar.category6 { fill: #8ca252; } + +.epoch.category20b div.ref.category7 { background-color: #b5cf6b; } + +.epoch.category20b .category7 .line { stroke: #b5cf6b; } + +.epoch.category20b .category7 .area, .epoch.category20b .category7 .dot { fill: #b5cf6b; stroke: transparent; } + +.epoch.category20b .arc.category7 path { fill: #b5cf6b; } + +.epoch.category20b .bar.category7 { fill: #b5cf6b; } + +.epoch.category20b div.ref.category8 { background-color: #cedb9c; } + +.epoch.category20b .category8 .line { stroke: #cedb9c; } + +.epoch.category20b .category8 .area, .epoch.category20b .category8 .dot { fill: #cedb9c; stroke: transparent; } + +.epoch.category20b .arc.category8 path { fill: #cedb9c; } + +.epoch.category20b .bar.category8 { fill: #cedb9c; } + +.epoch.category20b div.ref.category9 { background-color: #8c6d31; } + +.epoch.category20b .category9 .line { stroke: #8c6d31; } + +.epoch.category20b .category9 .area, .epoch.category20b .category9 .dot { fill: #8c6d31; stroke: transparent; } + +.epoch.category20b .arc.category9 path { fill: #8c6d31; } + +.epoch.category20b .bar.category9 { fill: #8c6d31; } + +.epoch.category20b div.ref.category10 { background-color: #bd9e39; } + +.epoch.category20b .category10 .line { stroke: #bd9e39; } + +.epoch.category20b .category10 .area, .epoch.category20b .category10 .dot { fill: #bd9e39; stroke: transparent; } + +.epoch.category20b .arc.category10 path { fill: #bd9e39; } + +.epoch.category20b .bar.category10 { fill: #bd9e39; } + +.epoch.category20b div.ref.category11 { background-color: #e7ba52; } + +.epoch.category20b .category11 .line { stroke: #e7ba52; } + +.epoch.category20b .category11 .area, .epoch.category20b .category11 .dot { fill: #e7ba52; stroke: transparent; } + +.epoch.category20b .arc.category11 path { fill: #e7ba52; } + +.epoch.category20b .bar.category11 { fill: #e7ba52; } + +.epoch.category20b div.ref.category12 { background-color: #e7cb94; } + +.epoch.category20b .category12 .line { stroke: #e7cb94; } + +.epoch.category20b .category12 .area, .epoch.category20b .category12 .dot { fill: #e7cb94; stroke: transparent; } + +.epoch.category20b .arc.category12 path { fill: #e7cb94; } + +.epoch.category20b .bar.category12 { fill: #e7cb94; } + +.epoch.category20b div.ref.category13 { background-color: #843c39; } + +.epoch.category20b .category13 .line { stroke: #843c39; } + +.epoch.category20b .category13 .area, .epoch.category20b .category13 .dot { fill: #843c39; stroke: transparent; } + +.epoch.category20b .arc.category13 path { fill: #843c39; } + +.epoch.category20b .bar.category13 { fill: #843c39; } + +.epoch.category20b div.ref.category14 { background-color: #ad494a; } + +.epoch.category20b .category14 .line { stroke: #ad494a; } + +.epoch.category20b .category14 .area, .epoch.category20b .category14 .dot { fill: #ad494a; stroke: transparent; } + +.epoch.category20b .arc.category14 path { fill: #ad494a; } + +.epoch.category20b .bar.category14 { fill: #ad494a; } + +.epoch.category20b div.ref.category15 { background-color: #d6616b; } + +.epoch.category20b .category15 .line { stroke: #d6616b; } + +.epoch.category20b .category15 .area, .epoch.category20b .category15 .dot { fill: #d6616b; stroke: transparent; } + +.epoch.category20b .arc.category15 path { fill: #d6616b; } + +.epoch.category20b .bar.category15 { fill: #d6616b; } + +.epoch.category20b div.ref.category16 { background-color: #e7969c; } + +.epoch.category20b .category16 .line { stroke: #e7969c; } + +.epoch.category20b .category16 .area, .epoch.category20b .category16 .dot { fill: #e7969c; stroke: transparent; } + +.epoch.category20b .arc.category16 path { fill: #e7969c; } + +.epoch.category20b .bar.category16 { fill: #e7969c; } + +.epoch.category20b div.ref.category17 { background-color: #7b4173; } + +.epoch.category20b .category17 .line { stroke: #7b4173; } + +.epoch.category20b .category17 .area, .epoch.category20b .category17 .dot { fill: #7b4173; stroke: transparent; } + +.epoch.category20b .arc.category17 path { fill: #7b4173; } + +.epoch.category20b .bar.category17 { fill: #7b4173; } + +.epoch.category20b div.ref.category18 { background-color: #a55194; } + +.epoch.category20b .category18 .line { stroke: #a55194; } + +.epoch.category20b .category18 .area, .epoch.category20b .category18 .dot { fill: #a55194; stroke: transparent; } + +.epoch.category20b .arc.category18 path { fill: #a55194; } + +.epoch.category20b .bar.category18 { fill: #a55194; } + +.epoch.category20b div.ref.category19 { background-color: #ce6dbd; } + +.epoch.category20b .category19 .line { stroke: #ce6dbd; } + +.epoch.category20b .category19 .area, .epoch.category20b .category19 .dot { fill: #ce6dbd; stroke: transparent; } + +.epoch.category20b .arc.category19 path { fill: #ce6dbd; } + +.epoch.category20b .bar.category19 { fill: #ce6dbd; } + +.epoch.category20b div.ref.category20 { background-color: #de9ed6; } + +.epoch.category20b .category20 .line { stroke: #de9ed6; } + +.epoch.category20b .category20 .area, .epoch.category20b .category20 .dot { fill: #de9ed6; stroke: transparent; } + +.epoch.category20b .arc.category20 path { fill: #de9ed6; } + +.epoch.category20b .bar.category20 { fill: #de9ed6; } + +.epoch.category20c div.ref.category1 { background-color: #3182bd; } + +.epoch.category20c .category1 .line { stroke: #3182bd; } + +.epoch.category20c .category1 .area, .epoch.category20c .category1 .dot { fill: #3182bd; stroke: transparent; } + +.epoch.category20c .arc.category1 path { fill: #3182bd; } + +.epoch.category20c .bar.category1 { fill: #3182bd; } + +.epoch.category20c div.ref.category2 { background-color: #6baed6; } + +.epoch.category20c .category2 .line { stroke: #6baed6; } + +.epoch.category20c .category2 .area, .epoch.category20c .category2 .dot { fill: #6baed6; stroke: transparent; } + +.epoch.category20c .arc.category2 path { fill: #6baed6; } + +.epoch.category20c .bar.category2 { fill: #6baed6; } + +.epoch.category20c div.ref.category3 { background-color: #9ecae1; } + +.epoch.category20c .category3 .line { stroke: #9ecae1; } + +.epoch.category20c .category3 .area, .epoch.category20c .category3 .dot { fill: #9ecae1; stroke: transparent; } + +.epoch.category20c .arc.category3 path { fill: #9ecae1; } + +.epoch.category20c .bar.category3 { fill: #9ecae1; } + +.epoch.category20c div.ref.category4 { background-color: #c6dbef; } + +.epoch.category20c .category4 .line { stroke: #c6dbef; } + +.epoch.category20c .category4 .area, .epoch.category20c .category4 .dot { fill: #c6dbef; stroke: transparent; } + +.epoch.category20c .arc.category4 path { fill: #c6dbef; } + +.epoch.category20c .bar.category4 { fill: #c6dbef; } + +.epoch.category20c div.ref.category5 { background-color: #e6550d; } + +.epoch.category20c .category5 .line { stroke: #e6550d; } + +.epoch.category20c .category5 .area, .epoch.category20c .category5 .dot { fill: #e6550d; stroke: transparent; } + +.epoch.category20c .arc.category5 path { fill: #e6550d; } + +.epoch.category20c .bar.category5 { fill: #e6550d; } + +.epoch.category20c div.ref.category6 { background-color: #fd8d3c; } + +.epoch.category20c .category6 .line { stroke: #fd8d3c; } + +.epoch.category20c .category6 .area, .epoch.category20c .category6 .dot { fill: #fd8d3c; stroke: transparent; } + +.epoch.category20c .arc.category6 path { fill: #fd8d3c; } + +.epoch.category20c .bar.category6 { fill: #fd8d3c; } + +.epoch.category20c div.ref.category7 { background-color: #fdae6b; } + +.epoch.category20c .category7 .line { stroke: #fdae6b; } + +.epoch.category20c .category7 .area, .epoch.category20c .category7 .dot { fill: #fdae6b; stroke: transparent; } + +.epoch.category20c .arc.category7 path { fill: #fdae6b; } + +.epoch.category20c .bar.category7 { fill: #fdae6b; } + +.epoch.category20c div.ref.category8 { background-color: #fdd0a2; } + +.epoch.category20c .category8 .line { stroke: #fdd0a2; } + +.epoch.category20c .category8 .area, .epoch.category20c .category8 .dot { fill: #fdd0a2; stroke: transparent; } + +.epoch.category20c .arc.category8 path { fill: #fdd0a2; } + +.epoch.category20c .bar.category8 { fill: #fdd0a2; } + +.epoch.category20c div.ref.category9 { background-color: #31a354; } + +.epoch.category20c .category9 .line { stroke: #31a354; } + +.epoch.category20c .category9 .area, .epoch.category20c .category9 .dot { fill: #31a354; stroke: transparent; } + +.epoch.category20c .arc.category9 path { fill: #31a354; } + +.epoch.category20c .bar.category9 { fill: #31a354; } + +.epoch.category20c div.ref.category10 { background-color: #74c476; } + +.epoch.category20c .category10 .line { stroke: #74c476; } + +.epoch.category20c .category10 .area, .epoch.category20c .category10 .dot { fill: #74c476; stroke: transparent; } + +.epoch.category20c .arc.category10 path { fill: #74c476; } + +.epoch.category20c .bar.category10 { fill: #74c476; } + +.epoch.category20c div.ref.category11 { background-color: #a1d99b; } + +.epoch.category20c .category11 .line { stroke: #a1d99b; } + +.epoch.category20c .category11 .area, .epoch.category20c .category11 .dot { fill: #a1d99b; stroke: transparent; } + +.epoch.category20c .arc.category11 path { fill: #a1d99b; } + +.epoch.category20c .bar.category11 { fill: #a1d99b; } + +.epoch.category20c div.ref.category12 { background-color: #c7e9c0; } + +.epoch.category20c .category12 .line { stroke: #c7e9c0; } + +.epoch.category20c .category12 .area, .epoch.category20c .category12 .dot { fill: #c7e9c0; stroke: transparent; } + +.epoch.category20c .arc.category12 path { fill: #c7e9c0; } + +.epoch.category20c .bar.category12 { fill: #c7e9c0; } + +.epoch.category20c div.ref.category13 { background-color: #756bb1; } + +.epoch.category20c .category13 .line { stroke: #756bb1; } + +.epoch.category20c .category13 .area, .epoch.category20c .category13 .dot { fill: #756bb1; stroke: transparent; } + +.epoch.category20c .arc.category13 path { fill: #756bb1; } + +.epoch.category20c .bar.category13 { fill: #756bb1; } + +.epoch.category20c div.ref.category14 { background-color: #9e9ac8; } + +.epoch.category20c .category14 .line { stroke: #9e9ac8; } + +.epoch.category20c .category14 .area, .epoch.category20c .category14 .dot { fill: #9e9ac8; stroke: transparent; } + +.epoch.category20c .arc.category14 path { fill: #9e9ac8; } + +.epoch.category20c .bar.category14 { fill: #9e9ac8; } + +.epoch.category20c div.ref.category15 { background-color: #bcbddc; } + +.epoch.category20c .category15 .line { stroke: #bcbddc; } + +.epoch.category20c .category15 .area, .epoch.category20c .category15 .dot { fill: #bcbddc; stroke: transparent; } + +.epoch.category20c .arc.category15 path { fill: #bcbddc; } + +.epoch.category20c .bar.category15 { fill: #bcbddc; } + +.epoch.category20c div.ref.category16 { background-color: #dadaeb; } + +.epoch.category20c .category16 .line { stroke: #dadaeb; } + +.epoch.category20c .category16 .area, .epoch.category20c .category16 .dot { fill: #dadaeb; stroke: transparent; } + +.epoch.category20c .arc.category16 path { fill: #dadaeb; } + +.epoch.category20c .bar.category16 { fill: #dadaeb; } + +.epoch.category20c div.ref.category17 { background-color: #636363; } + +.epoch.category20c .category17 .line { stroke: #636363; } + +.epoch.category20c .category17 .area, .epoch.category20c .category17 .dot { fill: #636363; stroke: transparent; } + +.epoch.category20c .arc.category17 path { fill: #636363; } + +.epoch.category20c .bar.category17 { fill: #636363; } + +.epoch.category20c div.ref.category18 { background-color: #969696; } + +.epoch.category20c .category18 .line { stroke: #969696; } + +.epoch.category20c .category18 .area, .epoch.category20c .category18 .dot { fill: #969696; stroke: transparent; } + +.epoch.category20c .arc.category18 path { fill: #969696; } + +.epoch.category20c .bar.category18 { fill: #969696; } + +.epoch.category20c div.ref.category19 { background-color: #bdbdbd; } + +.epoch.category20c .category19 .line { stroke: #bdbdbd; } + +.epoch.category20c .category19 .area, .epoch.category20c .category19 .dot { fill: #bdbdbd; stroke: transparent; } + +.epoch.category20c .arc.category19 path { fill: #bdbdbd; } + +.epoch.category20c .bar.category19 { fill: #bdbdbd; } + +.epoch.category20c div.ref.category20 { background-color: #d9d9d9; } + +.epoch.category20c .category20 .line { stroke: #d9d9d9; } + +.epoch.category20c .category20 .area, .epoch.category20c .category20 .dot { fill: #d9d9d9; stroke: transparent; } + +.epoch.category20c .arc.category20 path { fill: #d9d9d9; } + +.epoch.category20c .bar.category20 { fill: #d9d9d9; } + +/* Heatmap Colors The heatmap real-time graph uses color blending to choose the color for which to render each bucket. Basic d3 categorical colors do not work well in this instance because the colors in the sequence vary wildly in precieved luminosity. Below we define small subsets that should work well together because they are of similar luminosities. Note: darker colors work better since we use opacity to denote "height" or "intensity" for each bucket after picking a mix color. */ +.epoch .category1 .bucket, .epoch.heatmap5 .category1 .bucket { fill: #1f77b4; } + +.epoch .category2 .bucket, .epoch.heatmap5 .category2 .bucket { fill: #2ca02c; } + +.epoch .category3 .bucket, .epoch.heatmap5 .category3 .bucket { fill: #d62728; } + +.epoch .category4 .bucket, .epoch.heatmap5 .category4 .bucket { fill: #8c564b; } + +.epoch .category5 .bucket, .epoch.heatmap5 .category5 .bucket { fill: #7f7f7f; } + +/* theme/_dark.scss - Theme design for dark page backgrounds. Designed by Ryan Sandor Richards */ +.epoch-theme-dark .epoch .axis path, .epoch-theme-dark .epoch .axis line { stroke: #d0d0d0; } + +.epoch-theme-dark .epoch .axis .tick text { fill: #d0d0d0; } + +.epoch-theme-dark .arc.pie { stroke: #333; } + +.epoch-theme-dark .arc.pie text { fill: #333; } + +.epoch-theme-dark .epoch .gauge-labels .value { fill: #BBB; } + +.epoch-theme-dark .epoch .gauge .arc.outer { stroke: #999; } + +.epoch-theme-dark .epoch .gauge .arc.inner { stroke: #AAA; } + +.epoch-theme-dark .epoch .gauge .tick { stroke: #AAA; } + +.epoch-theme-dark .epoch .gauge .needle { fill: #F3DE88; } + +.epoch-theme-dark .epoch .gauge .needle-base { fill: #999; } + +.epoch-theme-dark .epoch div.ref.category1, .epoch-theme-dark .epoch.category10 div.ref.category1 { background-color: #909CFF; } + +.epoch-theme-dark .epoch .category1 .line, .epoch-theme-dark .epoch.category10 .category1 .line { stroke: #909CFF; } + +.epoch-theme-dark .epoch .category1 .area, .epoch-theme-dark .epoch .category1 .dot, .epoch-theme-dark .epoch.category10 .category1 .area, .epoch-theme-dark .epoch.category10 .category1 .dot { fill: #909CFF; stroke: transparent; } + +.epoch-theme-dark .epoch .arc.category1 path, .epoch-theme-dark .epoch.category10 .arc.category1 path { fill: #909CFF; } + +.epoch-theme-dark .epoch .bar.category1, .epoch-theme-dark .epoch.category10 .bar.category1 { fill: #909CFF; } + +.epoch-theme-dark .epoch div.ref.category2, .epoch-theme-dark .epoch.category10 div.ref.category2 { background-color: #FFAC89; } + +.epoch-theme-dark .epoch .category2 .line, .epoch-theme-dark .epoch.category10 .category2 .line { stroke: #FFAC89; } + +.epoch-theme-dark .epoch .category2 .area, .epoch-theme-dark .epoch .category2 .dot, .epoch-theme-dark .epoch.category10 .category2 .area, .epoch-theme-dark .epoch.category10 .category2 .dot { fill: #FFAC89; stroke: transparent; } + +.epoch-theme-dark .epoch .arc.category2 path, .epoch-theme-dark .epoch.category10 .arc.category2 path { fill: #FFAC89; } + +.epoch-theme-dark .epoch .bar.category2, .epoch-theme-dark .epoch.category10 .bar.category2 { fill: #FFAC89; } + +.epoch-theme-dark .epoch div.ref.category3, .epoch-theme-dark .epoch.category10 div.ref.category3 { background-color: #E889E8; } + +.epoch-theme-dark .epoch .category3 .line, .epoch-theme-dark .epoch.category10 .category3 .line { stroke: #E889E8; } + +.epoch-theme-dark .epoch .category3 .area, .epoch-theme-dark .epoch .category3 .dot, .epoch-theme-dark .epoch.category10 .category3 .area, .epoch-theme-dark .epoch.category10 .category3 .dot { fill: #E889E8; stroke: transparent; } + +.epoch-theme-dark .epoch .arc.category3 path, .epoch-theme-dark .epoch.category10 .arc.category3 path { fill: #E889E8; } + +.epoch-theme-dark .epoch .bar.category3, .epoch-theme-dark .epoch.category10 .bar.category3 { fill: #E889E8; } + +.epoch-theme-dark .epoch div.ref.category4, .epoch-theme-dark .epoch.category10 div.ref.category4 { background-color: #78E8D3; } + +.epoch-theme-dark .epoch .category4 .line, .epoch-theme-dark .epoch.category10 .category4 .line { stroke: #78E8D3; } + +.epoch-theme-dark .epoch .category4 .area, .epoch-theme-dark .epoch .category4 .dot, .epoch-theme-dark .epoch.category10 .category4 .area, .epoch-theme-dark .epoch.category10 .category4 .dot { fill: #78E8D3; stroke: transparent; } + +.epoch-theme-dark .epoch .arc.category4 path, .epoch-theme-dark .epoch.category10 .arc.category4 path { fill: #78E8D3; } + +.epoch-theme-dark .epoch .bar.category4, .epoch-theme-dark .epoch.category10 .bar.category4 { fill: #78E8D3; } + +.epoch-theme-dark .epoch div.ref.category5, .epoch-theme-dark .epoch.category10 div.ref.category5 { background-color: #C2FF97; } + +.epoch-theme-dark .epoch .category5 .line, .epoch-theme-dark .epoch.category10 .category5 .line { stroke: #C2FF97; } + +.epoch-theme-dark .epoch .category5 .area, .epoch-theme-dark .epoch .category5 .dot, .epoch-theme-dark .epoch.category10 .category5 .area, .epoch-theme-dark .epoch.category10 .category5 .dot { fill: #C2FF97; stroke: transparent; } + +.epoch-theme-dark .epoch .arc.category5 path, .epoch-theme-dark .epoch.category10 .arc.category5 path { fill: #C2FF97; } + +.epoch-theme-dark .epoch .bar.category5, .epoch-theme-dark .epoch.category10 .bar.category5 { fill: #C2FF97; } + +.epoch-theme-dark .epoch div.ref.category6, .epoch-theme-dark .epoch.category10 div.ref.category6 { background-color: #B7BCD1; } + +.epoch-theme-dark .epoch .category6 .line, .epoch-theme-dark .epoch.category10 .category6 .line { stroke: #B7BCD1; } + +.epoch-theme-dark .epoch .category6 .area, .epoch-theme-dark .epoch .category6 .dot, .epoch-theme-dark .epoch.category10 .category6 .area, .epoch-theme-dark .epoch.category10 .category6 .dot { fill: #B7BCD1; stroke: transparent; } + +.epoch-theme-dark .epoch .arc.category6 path, .epoch-theme-dark .epoch.category10 .arc.category6 path { fill: #B7BCD1; } + +.epoch-theme-dark .epoch .bar.category6, .epoch-theme-dark .epoch.category10 .bar.category6 { fill: #B7BCD1; } + +.epoch-theme-dark .epoch div.ref.category7, .epoch-theme-dark .epoch.category10 div.ref.category7 { background-color: #FF857F; } + +.epoch-theme-dark .epoch .category7 .line, .epoch-theme-dark .epoch.category10 .category7 .line { stroke: #FF857F; } + +.epoch-theme-dark .epoch .category7 .area, .epoch-theme-dark .epoch .category7 .dot, .epoch-theme-dark .epoch.category10 .category7 .area, .epoch-theme-dark .epoch.category10 .category7 .dot { fill: #FF857F; stroke: transparent; } + +.epoch-theme-dark .epoch .arc.category7 path, .epoch-theme-dark .epoch.category10 .arc.category7 path { fill: #FF857F; } + +.epoch-theme-dark .epoch .bar.category7, .epoch-theme-dark .epoch.category10 .bar.category7 { fill: #FF857F; } + +.epoch-theme-dark .epoch div.ref.category8, .epoch-theme-dark .epoch.category10 div.ref.category8 { background-color: #F3DE88; } + +.epoch-theme-dark .epoch .category8 .line, .epoch-theme-dark .epoch.category10 .category8 .line { stroke: #F3DE88; } + +.epoch-theme-dark .epoch .category8 .area, .epoch-theme-dark .epoch .category8 .dot, .epoch-theme-dark .epoch.category10 .category8 .area, .epoch-theme-dark .epoch.category10 .category8 .dot { fill: #F3DE88; stroke: transparent; } + +.epoch-theme-dark .epoch .arc.category8 path, .epoch-theme-dark .epoch.category10 .arc.category8 path { fill: #F3DE88; } + +.epoch-theme-dark .epoch .bar.category8, .epoch-theme-dark .epoch.category10 .bar.category8 { fill: #F3DE88; } + +.epoch-theme-dark .epoch div.ref.category9, .epoch-theme-dark .epoch.category10 div.ref.category9 { background-color: #C9935E; } + +.epoch-theme-dark .epoch .category9 .line, .epoch-theme-dark .epoch.category10 .category9 .line { stroke: #C9935E; } + +.epoch-theme-dark .epoch .category9 .area, .epoch-theme-dark .epoch .category9 .dot, .epoch-theme-dark .epoch.category10 .category9 .area, .epoch-theme-dark .epoch.category10 .category9 .dot { fill: #C9935E; stroke: transparent; } + +.epoch-theme-dark .epoch .arc.category9 path, .epoch-theme-dark .epoch.category10 .arc.category9 path { fill: #C9935E; } + +.epoch-theme-dark .epoch .bar.category9, .epoch-theme-dark .epoch.category10 .bar.category9 { fill: #C9935E; } + +.epoch-theme-dark .epoch div.ref.category10, .epoch-theme-dark .epoch.category10 div.ref.category10 { background-color: #A488FF; } + +.epoch-theme-dark .epoch .category10 .line, .epoch-theme-dark .epoch.category10 .category10 .line { stroke: #A488FF; } + +.epoch-theme-dark .epoch .category10 .area, .epoch-theme-dark .epoch .category10 .dot, .epoch-theme-dark .epoch.category10 .category10 .area, .epoch-theme-dark .epoch.category10 .category10 .dot { fill: #A488FF; stroke: transparent; } + +.epoch-theme-dark .epoch .arc.category10 path, .epoch-theme-dark .epoch.category10 .arc.category10 path { fill: #A488FF; } + +.epoch-theme-dark .epoch .bar.category10, .epoch-theme-dark .epoch.category10 .bar.category10 { fill: #A488FF; } + +.epoch-theme-dark .epoch.category20 div.ref.category1 { background-color: #909CFF; } + +.epoch-theme-dark .epoch.category20 .category1 .line { stroke: #909CFF; } + +.epoch-theme-dark .epoch.category20 .category1 .area, .epoch-theme-dark .epoch.category20 .category1 .dot { fill: #909CFF; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category1 path { fill: #909CFF; } + +.epoch-theme-dark .epoch.category20 .bar.category1 { fill: #909CFF; } + +.epoch-theme-dark .epoch.category20 div.ref.category2 { background-color: #626AAD; } + +.epoch-theme-dark .epoch.category20 .category2 .line { stroke: #626AAD; } + +.epoch-theme-dark .epoch.category20 .category2 .area, .epoch-theme-dark .epoch.category20 .category2 .dot { fill: #626AAD; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category2 path { fill: #626AAD; } + +.epoch-theme-dark .epoch.category20 .bar.category2 { fill: #626AAD; } + +.epoch-theme-dark .epoch.category20 div.ref.category3 { background-color: #FFAC89; } + +.epoch-theme-dark .epoch.category20 .category3 .line { stroke: #FFAC89; } + +.epoch-theme-dark .epoch.category20 .category3 .area, .epoch-theme-dark .epoch.category20 .category3 .dot { fill: #FFAC89; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category3 path { fill: #FFAC89; } + +.epoch-theme-dark .epoch.category20 .bar.category3 { fill: #FFAC89; } + +.epoch-theme-dark .epoch.category20 div.ref.category4 { background-color: #BD7F66; } + +.epoch-theme-dark .epoch.category20 .category4 .line { stroke: #BD7F66; } + +.epoch-theme-dark .epoch.category20 .category4 .area, .epoch-theme-dark .epoch.category20 .category4 .dot { fill: #BD7F66; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category4 path { fill: #BD7F66; } + +.epoch-theme-dark .epoch.category20 .bar.category4 { fill: #BD7F66; } + +.epoch-theme-dark .epoch.category20 div.ref.category5 { background-color: #E889E8; } + +.epoch-theme-dark .epoch.category20 .category5 .line { stroke: #E889E8; } + +.epoch-theme-dark .epoch.category20 .category5 .area, .epoch-theme-dark .epoch.category20 .category5 .dot { fill: #E889E8; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category5 path { fill: #E889E8; } + +.epoch-theme-dark .epoch.category20 .bar.category5 { fill: #E889E8; } + +.epoch-theme-dark .epoch.category20 div.ref.category6 { background-color: #995A99; } + +.epoch-theme-dark .epoch.category20 .category6 .line { stroke: #995A99; } + +.epoch-theme-dark .epoch.category20 .category6 .area, .epoch-theme-dark .epoch.category20 .category6 .dot { fill: #995A99; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category6 path { fill: #995A99; } + +.epoch-theme-dark .epoch.category20 .bar.category6 { fill: #995A99; } + +.epoch-theme-dark .epoch.category20 div.ref.category7 { background-color: #78E8D3; } + +.epoch-theme-dark .epoch.category20 .category7 .line { stroke: #78E8D3; } + +.epoch-theme-dark .epoch.category20 .category7 .area, .epoch-theme-dark .epoch.category20 .category7 .dot { fill: #78E8D3; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category7 path { fill: #78E8D3; } + +.epoch-theme-dark .epoch.category20 .bar.category7 { fill: #78E8D3; } + +.epoch-theme-dark .epoch.category20 div.ref.category8 { background-color: #4F998C; } + +.epoch-theme-dark .epoch.category20 .category8 .line { stroke: #4F998C; } + +.epoch-theme-dark .epoch.category20 .category8 .area, .epoch-theme-dark .epoch.category20 .category8 .dot { fill: #4F998C; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category8 path { fill: #4F998C; } + +.epoch-theme-dark .epoch.category20 .bar.category8 { fill: #4F998C; } + +.epoch-theme-dark .epoch.category20 div.ref.category9 { background-color: #C2FF97; } + +.epoch-theme-dark .epoch.category20 .category9 .line { stroke: #C2FF97; } + +.epoch-theme-dark .epoch.category20 .category9 .area, .epoch-theme-dark .epoch.category20 .category9 .dot { fill: #C2FF97; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category9 path { fill: #C2FF97; } + +.epoch-theme-dark .epoch.category20 .bar.category9 { fill: #C2FF97; } + +.epoch-theme-dark .epoch.category20 div.ref.category10 { background-color: #789E5E; } + +.epoch-theme-dark .epoch.category20 .category10 .line { stroke: #789E5E; } + +.epoch-theme-dark .epoch.category20 .category10 .area, .epoch-theme-dark .epoch.category20 .category10 .dot { fill: #789E5E; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category10 path { fill: #789E5E; } + +.epoch-theme-dark .epoch.category20 .bar.category10 { fill: #789E5E; } + +.epoch-theme-dark .epoch.category20 div.ref.category11 { background-color: #B7BCD1; } + +.epoch-theme-dark .epoch.category20 .category11 .line { stroke: #B7BCD1; } + +.epoch-theme-dark .epoch.category20 .category11 .area, .epoch-theme-dark .epoch.category20 .category11 .dot { fill: #B7BCD1; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category11 path { fill: #B7BCD1; } + +.epoch-theme-dark .epoch.category20 .bar.category11 { fill: #B7BCD1; } + +.epoch-theme-dark .epoch.category20 div.ref.category12 { background-color: #7F8391; } + +.epoch-theme-dark .epoch.category20 .category12 .line { stroke: #7F8391; } + +.epoch-theme-dark .epoch.category20 .category12 .area, .epoch-theme-dark .epoch.category20 .category12 .dot { fill: #7F8391; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category12 path { fill: #7F8391; } + +.epoch-theme-dark .epoch.category20 .bar.category12 { fill: #7F8391; } + +.epoch-theme-dark .epoch.category20 div.ref.category13 { background-color: #CCB889; } + +.epoch-theme-dark .epoch.category20 .category13 .line { stroke: #CCB889; } + +.epoch-theme-dark .epoch.category20 .category13 .area, .epoch-theme-dark .epoch.category20 .category13 .dot { fill: #CCB889; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category13 path { fill: #CCB889; } + +.epoch-theme-dark .epoch.category20 .bar.category13 { fill: #CCB889; } + +.epoch-theme-dark .epoch.category20 div.ref.category14 { background-color: #A1906B; } + +.epoch-theme-dark .epoch.category20 .category14 .line { stroke: #A1906B; } + +.epoch-theme-dark .epoch.category20 .category14 .area, .epoch-theme-dark .epoch.category20 .category14 .dot { fill: #A1906B; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category14 path { fill: #A1906B; } + +.epoch-theme-dark .epoch.category20 .bar.category14 { fill: #A1906B; } + +.epoch-theme-dark .epoch.category20 div.ref.category15 { background-color: #F3DE88; } + +.epoch-theme-dark .epoch.category20 .category15 .line { stroke: #F3DE88; } + +.epoch-theme-dark .epoch.category20 .category15 .area, .epoch-theme-dark .epoch.category20 .category15 .dot { fill: #F3DE88; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category15 path { fill: #F3DE88; } + +.epoch-theme-dark .epoch.category20 .bar.category15 { fill: #F3DE88; } + +.epoch-theme-dark .epoch.category20 div.ref.category16 { background-color: #A89A5E; } + +.epoch-theme-dark .epoch.category20 .category16 .line { stroke: #A89A5E; } + +.epoch-theme-dark .epoch.category20 .category16 .area, .epoch-theme-dark .epoch.category20 .category16 .dot { fill: #A89A5E; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category16 path { fill: #A89A5E; } + +.epoch-theme-dark .epoch.category20 .bar.category16 { fill: #A89A5E; } + +.epoch-theme-dark .epoch.category20 div.ref.category17 { background-color: #FF857F; } + +.epoch-theme-dark .epoch.category20 .category17 .line { stroke: #FF857F; } + +.epoch-theme-dark .epoch.category20 .category17 .area, .epoch-theme-dark .epoch.category20 .category17 .dot { fill: #FF857F; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category17 path { fill: #FF857F; } + +.epoch-theme-dark .epoch.category20 .bar.category17 { fill: #FF857F; } + +.epoch-theme-dark .epoch.category20 div.ref.category18 { background-color: #BA615D; } + +.epoch-theme-dark .epoch.category20 .category18 .line { stroke: #BA615D; } + +.epoch-theme-dark .epoch.category20 .category18 .area, .epoch-theme-dark .epoch.category20 .category18 .dot { fill: #BA615D; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category18 path { fill: #BA615D; } + +.epoch-theme-dark .epoch.category20 .bar.category18 { fill: #BA615D; } + +.epoch-theme-dark .epoch.category20 div.ref.category19 { background-color: #A488FF; } + +.epoch-theme-dark .epoch.category20 .category19 .line { stroke: #A488FF; } + +.epoch-theme-dark .epoch.category20 .category19 .area, .epoch-theme-dark .epoch.category20 .category19 .dot { fill: #A488FF; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category19 path { fill: #A488FF; } + +.epoch-theme-dark .epoch.category20 .bar.category19 { fill: #A488FF; } + +.epoch-theme-dark .epoch.category20 div.ref.category20 { background-color: #7662B8; } + +.epoch-theme-dark .epoch.category20 .category20 .line { stroke: #7662B8; } + +.epoch-theme-dark .epoch.category20 .category20 .area, .epoch-theme-dark .epoch.category20 .category20 .dot { fill: #7662B8; stroke: transparent; } + +.epoch-theme-dark .epoch.category20 .arc.category20 path { fill: #7662B8; } + +.epoch-theme-dark .epoch.category20 .bar.category20 { fill: #7662B8; } + +.epoch-theme-dark .epoch.category20b div.ref.category1 { background-color: #909CFF; } + +.epoch-theme-dark .epoch.category20b .category1 .line { stroke: #909CFF; } + +.epoch-theme-dark .epoch.category20b .category1 .area, .epoch-theme-dark .epoch.category20b .category1 .dot { fill: #909CFF; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category1 path { fill: #909CFF; } + +.epoch-theme-dark .epoch.category20b .bar.category1 { fill: #909CFF; } + +.epoch-theme-dark .epoch.category20b div.ref.category2 { background-color: #7680D1; } + +.epoch-theme-dark .epoch.category20b .category2 .line { stroke: #7680D1; } + +.epoch-theme-dark .epoch.category20b .category2 .area, .epoch-theme-dark .epoch.category20b .category2 .dot { fill: #7680D1; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category2 path { fill: #7680D1; } + +.epoch-theme-dark .epoch.category20b .bar.category2 { fill: #7680D1; } + +.epoch-theme-dark .epoch.category20b div.ref.category3 { background-color: #656DB2; } + +.epoch-theme-dark .epoch.category20b .category3 .line { stroke: #656DB2; } + +.epoch-theme-dark .epoch.category20b .category3 .area, .epoch-theme-dark .epoch.category20b .category3 .dot { fill: #656DB2; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category3 path { fill: #656DB2; } + +.epoch-theme-dark .epoch.category20b .bar.category3 { fill: #656DB2; } + +.epoch-theme-dark .epoch.category20b div.ref.category4 { background-color: #525992; } + +.epoch-theme-dark .epoch.category20b .category4 .line { stroke: #525992; } + +.epoch-theme-dark .epoch.category20b .category4 .area, .epoch-theme-dark .epoch.category20b .category4 .dot { fill: #525992; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category4 path { fill: #525992; } + +.epoch-theme-dark .epoch.category20b .bar.category4 { fill: #525992; } + +.epoch-theme-dark .epoch.category20b div.ref.category5 { background-color: #FFAC89; } + +.epoch-theme-dark .epoch.category20b .category5 .line { stroke: #FFAC89; } + +.epoch-theme-dark .epoch.category20b .category5 .area, .epoch-theme-dark .epoch.category20b .category5 .dot { fill: #FFAC89; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category5 path { fill: #FFAC89; } + +.epoch-theme-dark .epoch.category20b .bar.category5 { fill: #FFAC89; } + +.epoch-theme-dark .epoch.category20b div.ref.category6 { background-color: #D18D71; } + +.epoch-theme-dark .epoch.category20b .category6 .line { stroke: #D18D71; } + +.epoch-theme-dark .epoch.category20b .category6 .area, .epoch-theme-dark .epoch.category20b .category6 .dot { fill: #D18D71; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category6 path { fill: #D18D71; } + +.epoch-theme-dark .epoch.category20b .bar.category6 { fill: #D18D71; } + +.epoch-theme-dark .epoch.category20b div.ref.category7 { background-color: #AB735C; } + +.epoch-theme-dark .epoch.category20b .category7 .line { stroke: #AB735C; } + +.epoch-theme-dark .epoch.category20b .category7 .area, .epoch-theme-dark .epoch.category20b .category7 .dot { fill: #AB735C; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category7 path { fill: #AB735C; } + +.epoch-theme-dark .epoch.category20b .bar.category7 { fill: #AB735C; } + +.epoch-theme-dark .epoch.category20b div.ref.category8 { background-color: #92624E; } + +.epoch-theme-dark .epoch.category20b .category8 .line { stroke: #92624E; } + +.epoch-theme-dark .epoch.category20b .category8 .area, .epoch-theme-dark .epoch.category20b .category8 .dot { fill: #92624E; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category8 path { fill: #92624E; } + +.epoch-theme-dark .epoch.category20b .bar.category8 { fill: #92624E; } + +.epoch-theme-dark .epoch.category20b div.ref.category9 { background-color: #E889E8; } + +.epoch-theme-dark .epoch.category20b .category9 .line { stroke: #E889E8; } + +.epoch-theme-dark .epoch.category20b .category9 .area, .epoch-theme-dark .epoch.category20b .category9 .dot { fill: #E889E8; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category9 path { fill: #E889E8; } + +.epoch-theme-dark .epoch.category20b .bar.category9 { fill: #E889E8; } + +.epoch-theme-dark .epoch.category20b div.ref.category10 { background-color: #BA6EBA; } + +.epoch-theme-dark .epoch.category20b .category10 .line { stroke: #BA6EBA; } + +.epoch-theme-dark .epoch.category20b .category10 .area, .epoch-theme-dark .epoch.category20b .category10 .dot { fill: #BA6EBA; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category10 path { fill: #BA6EBA; } + +.epoch-theme-dark .epoch.category20b .bar.category10 { fill: #BA6EBA; } + +.epoch-theme-dark .epoch.category20b div.ref.category11 { background-color: #9B5C9B; } + +.epoch-theme-dark .epoch.category20b .category11 .line { stroke: #9B5C9B; } + +.epoch-theme-dark .epoch.category20b .category11 .area, .epoch-theme-dark .epoch.category20b .category11 .dot { fill: #9B5C9B; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category11 path { fill: #9B5C9B; } + +.epoch-theme-dark .epoch.category20b .bar.category11 { fill: #9B5C9B; } + +.epoch-theme-dark .epoch.category20b div.ref.category12 { background-color: #7B487B; } + +.epoch-theme-dark .epoch.category20b .category12 .line { stroke: #7B487B; } + +.epoch-theme-dark .epoch.category20b .category12 .area, .epoch-theme-dark .epoch.category20b .category12 .dot { fill: #7B487B; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category12 path { fill: #7B487B; } + +.epoch-theme-dark .epoch.category20b .bar.category12 { fill: #7B487B; } + +.epoch-theme-dark .epoch.category20b div.ref.category13 { background-color: #78E8D3; } + +.epoch-theme-dark .epoch.category20b .category13 .line { stroke: #78E8D3; } + +.epoch-theme-dark .epoch.category20b .category13 .area, .epoch-theme-dark .epoch.category20b .category13 .dot { fill: #78E8D3; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category13 path { fill: #78E8D3; } + +.epoch-theme-dark .epoch.category20b .bar.category13 { fill: #78E8D3; } + +.epoch-theme-dark .epoch.category20b div.ref.category14 { background-color: #60BAAA; } + +.epoch-theme-dark .epoch.category20b .category14 .line { stroke: #60BAAA; } + +.epoch-theme-dark .epoch.category20b .category14 .area, .epoch-theme-dark .epoch.category20b .category14 .dot { fill: #60BAAA; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category14 path { fill: #60BAAA; } + +.epoch-theme-dark .epoch.category20b .bar.category14 { fill: #60BAAA; } + +.epoch-theme-dark .epoch.category20b div.ref.category15 { background-color: #509B8D; } + +.epoch-theme-dark .epoch.category20b .category15 .line { stroke: #509B8D; } + +.epoch-theme-dark .epoch.category20b .category15 .area, .epoch-theme-dark .epoch.category20b .category15 .dot { fill: #509B8D; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category15 path { fill: #509B8D; } + +.epoch-theme-dark .epoch.category20b .bar.category15 { fill: #509B8D; } + +.epoch-theme-dark .epoch.category20b div.ref.category16 { background-color: #3F7B70; } + +.epoch-theme-dark .epoch.category20b .category16 .line { stroke: #3F7B70; } + +.epoch-theme-dark .epoch.category20b .category16 .area, .epoch-theme-dark .epoch.category20b .category16 .dot { fill: #3F7B70; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category16 path { fill: #3F7B70; } + +.epoch-theme-dark .epoch.category20b .bar.category16 { fill: #3F7B70; } + +.epoch-theme-dark .epoch.category20b div.ref.category17 { background-color: #C2FF97; } + +.epoch-theme-dark .epoch.category20b .category17 .line { stroke: #C2FF97; } + +.epoch-theme-dark .epoch.category20b .category17 .area, .epoch-theme-dark .epoch.category20b .category17 .dot { fill: #C2FF97; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category17 path { fill: #C2FF97; } + +.epoch-theme-dark .epoch.category20b .bar.category17 { fill: #C2FF97; } + +.epoch-theme-dark .epoch.category20b div.ref.category18 { background-color: #9FD17C; } + +.epoch-theme-dark .epoch.category20b .category18 .line { stroke: #9FD17C; } + +.epoch-theme-dark .epoch.category20b .category18 .area, .epoch-theme-dark .epoch.category20b .category18 .dot { fill: #9FD17C; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category18 path { fill: #9FD17C; } + +.epoch-theme-dark .epoch.category20b .bar.category18 { fill: #9FD17C; } + +.epoch-theme-dark .epoch.category20b div.ref.category19 { background-color: #7DA361; } + +.epoch-theme-dark .epoch.category20b .category19 .line { stroke: #7DA361; } + +.epoch-theme-dark .epoch.category20b .category19 .area, .epoch-theme-dark .epoch.category20b .category19 .dot { fill: #7DA361; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category19 path { fill: #7DA361; } + +.epoch-theme-dark .epoch.category20b .bar.category19 { fill: #7DA361; } + +.epoch-theme-dark .epoch.category20b div.ref.category20 { background-color: #65854E; } + +.epoch-theme-dark .epoch.category20b .category20 .line { stroke: #65854E; } + +.epoch-theme-dark .epoch.category20b .category20 .area, .epoch-theme-dark .epoch.category20b .category20 .dot { fill: #65854E; stroke: transparent; } + +.epoch-theme-dark .epoch.category20b .arc.category20 path { fill: #65854E; } + +.epoch-theme-dark .epoch.category20b .bar.category20 { fill: #65854E; } + +.epoch-theme-dark .epoch.category20c div.ref.category1 { background-color: #B7BCD1; } + +.epoch-theme-dark .epoch.category20c .category1 .line { stroke: #B7BCD1; } + +.epoch-theme-dark .epoch.category20c .category1 .area, .epoch-theme-dark .epoch.category20c .category1 .dot { fill: #B7BCD1; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category1 path { fill: #B7BCD1; } + +.epoch-theme-dark .epoch.category20c .bar.category1 { fill: #B7BCD1; } + +.epoch-theme-dark .epoch.category20c div.ref.category2 { background-color: #979DAD; } + +.epoch-theme-dark .epoch.category20c .category2 .line { stroke: #979DAD; } + +.epoch-theme-dark .epoch.category20c .category2 .area, .epoch-theme-dark .epoch.category20c .category2 .dot { fill: #979DAD; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category2 path { fill: #979DAD; } + +.epoch-theme-dark .epoch.category20c .bar.category2 { fill: #979DAD; } + +.epoch-theme-dark .epoch.category20c div.ref.category3 { background-color: #6E717D; } + +.epoch-theme-dark .epoch.category20c .category3 .line { stroke: #6E717D; } + +.epoch-theme-dark .epoch.category20c .category3 .area, .epoch-theme-dark .epoch.category20c .category3 .dot { fill: #6E717D; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category3 path { fill: #6E717D; } + +.epoch-theme-dark .epoch.category20c .bar.category3 { fill: #6E717D; } + +.epoch-theme-dark .epoch.category20c div.ref.category4 { background-color: #595C66; } + +.epoch-theme-dark .epoch.category20c .category4 .line { stroke: #595C66; } + +.epoch-theme-dark .epoch.category20c .category4 .area, .epoch-theme-dark .epoch.category20c .category4 .dot { fill: #595C66; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category4 path { fill: #595C66; } + +.epoch-theme-dark .epoch.category20c .bar.category4 { fill: #595C66; } + +.epoch-theme-dark .epoch.category20c div.ref.category5 { background-color: #FF857F; } + +.epoch-theme-dark .epoch.category20c .category5 .line { stroke: #FF857F; } + +.epoch-theme-dark .epoch.category20c .category5 .area, .epoch-theme-dark .epoch.category20c .category5 .dot { fill: #FF857F; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category5 path { fill: #FF857F; } + +.epoch-theme-dark .epoch.category20c .bar.category5 { fill: #FF857F; } + +.epoch-theme-dark .epoch.category20c div.ref.category6 { background-color: #DE746E; } + +.epoch-theme-dark .epoch.category20c .category6 .line { stroke: #DE746E; } + +.epoch-theme-dark .epoch.category20c .category6 .area, .epoch-theme-dark .epoch.category20c .category6 .dot { fill: #DE746E; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category6 path { fill: #DE746E; } + +.epoch-theme-dark .epoch.category20c .bar.category6 { fill: #DE746E; } + +.epoch-theme-dark .epoch.category20c div.ref.category7 { background-color: #B55F5A; } + +.epoch-theme-dark .epoch.category20c .category7 .line { stroke: #B55F5A; } + +.epoch-theme-dark .epoch.category20c .category7 .area, .epoch-theme-dark .epoch.category20c .category7 .dot { fill: #B55F5A; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category7 path { fill: #B55F5A; } + +.epoch-theme-dark .epoch.category20c .bar.category7 { fill: #B55F5A; } + +.epoch-theme-dark .epoch.category20c div.ref.category8 { background-color: #964E4B; } + +.epoch-theme-dark .epoch.category20c .category8 .line { stroke: #964E4B; } + +.epoch-theme-dark .epoch.category20c .category8 .area, .epoch-theme-dark .epoch.category20c .category8 .dot { fill: #964E4B; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category8 path { fill: #964E4B; } + +.epoch-theme-dark .epoch.category20c .bar.category8 { fill: #964E4B; } + +.epoch-theme-dark .epoch.category20c div.ref.category9 { background-color: #F3DE88; } + +.epoch-theme-dark .epoch.category20c .category9 .line { stroke: #F3DE88; } + +.epoch-theme-dark .epoch.category20c .category9 .area, .epoch-theme-dark .epoch.category20c .category9 .dot { fill: #F3DE88; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category9 path { fill: #F3DE88; } + +.epoch-theme-dark .epoch.category20c .bar.category9 { fill: #F3DE88; } + +.epoch-theme-dark .epoch.category20c div.ref.category10 { background-color: #DBC87B; } + +.epoch-theme-dark .epoch.category20c .category10 .line { stroke: #DBC87B; } + +.epoch-theme-dark .epoch.category20c .category10 .area, .epoch-theme-dark .epoch.category20c .category10 .dot { fill: #DBC87B; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category10 path { fill: #DBC87B; } + +.epoch-theme-dark .epoch.category20c .bar.category10 { fill: #DBC87B; } + +.epoch-theme-dark .epoch.category20c div.ref.category11 { background-color: #BAAA68; } + +.epoch-theme-dark .epoch.category20c .category11 .line { stroke: #BAAA68; } + +.epoch-theme-dark .epoch.category20c .category11 .area, .epoch-theme-dark .epoch.category20c .category11 .dot { fill: #BAAA68; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category11 path { fill: #BAAA68; } + +.epoch-theme-dark .epoch.category20c .bar.category11 { fill: #BAAA68; } + +.epoch-theme-dark .epoch.category20c div.ref.category12 { background-color: #918551; } + +.epoch-theme-dark .epoch.category20c .category12 .line { stroke: #918551; } + +.epoch-theme-dark .epoch.category20c .category12 .area, .epoch-theme-dark .epoch.category20c .category12 .dot { fill: #918551; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category12 path { fill: #918551; } + +.epoch-theme-dark .epoch.category20c .bar.category12 { fill: #918551; } + +.epoch-theme-dark .epoch.category20c div.ref.category13 { background-color: #C9935E; } + +.epoch-theme-dark .epoch.category20c .category13 .line { stroke: #C9935E; } + +.epoch-theme-dark .epoch.category20c .category13 .area, .epoch-theme-dark .epoch.category20c .category13 .dot { fill: #C9935E; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category13 path { fill: #C9935E; } + +.epoch-theme-dark .epoch.category20c .bar.category13 { fill: #C9935E; } + +.epoch-theme-dark .epoch.category20c div.ref.category14 { background-color: #B58455; } + +.epoch-theme-dark .epoch.category20c .category14 .line { stroke: #B58455; } + +.epoch-theme-dark .epoch.category20c .category14 .area, .epoch-theme-dark .epoch.category20c .category14 .dot { fill: #B58455; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category14 path { fill: #B58455; } + +.epoch-theme-dark .epoch.category20c .bar.category14 { fill: #B58455; } + +.epoch-theme-dark .epoch.category20c div.ref.category15 { background-color: #997048; } + +.epoch-theme-dark .epoch.category20c .category15 .line { stroke: #997048; } + +.epoch-theme-dark .epoch.category20c .category15 .area, .epoch-theme-dark .epoch.category20c .category15 .dot { fill: #997048; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category15 path { fill: #997048; } + +.epoch-theme-dark .epoch.category20c .bar.category15 { fill: #997048; } + +.epoch-theme-dark .epoch.category20c div.ref.category16 { background-color: #735436; } + +.epoch-theme-dark .epoch.category20c .category16 .line { stroke: #735436; } + +.epoch-theme-dark .epoch.category20c .category16 .area, .epoch-theme-dark .epoch.category20c .category16 .dot { fill: #735436; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category16 path { fill: #735436; } + +.epoch-theme-dark .epoch.category20c .bar.category16 { fill: #735436; } + +.epoch-theme-dark .epoch.category20c div.ref.category17 { background-color: #A488FF; } + +.epoch-theme-dark .epoch.category20c .category17 .line { stroke: #A488FF; } + +.epoch-theme-dark .epoch.category20c .category17 .area, .epoch-theme-dark .epoch.category20c .category17 .dot { fill: #A488FF; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category17 path { fill: #A488FF; } + +.epoch-theme-dark .epoch.category20c .bar.category17 { fill: #A488FF; } + +.epoch-theme-dark .epoch.category20c div.ref.category18 { background-color: #8670D1; } + +.epoch-theme-dark .epoch.category20c .category18 .line { stroke: #8670D1; } + +.epoch-theme-dark .epoch.category20c .category18 .area, .epoch-theme-dark .epoch.category20c .category18 .dot { fill: #8670D1; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category18 path { fill: #8670D1; } + +.epoch-theme-dark .epoch.category20c .bar.category18 { fill: #8670D1; } + +.epoch-theme-dark .epoch.category20c div.ref.category19 { background-color: #705CAD; } + +.epoch-theme-dark .epoch.category20c .category19 .line { stroke: #705CAD; } + +.epoch-theme-dark .epoch.category20c .category19 .area, .epoch-theme-dark .epoch.category20c .category19 .dot { fill: #705CAD; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category19 path { fill: #705CAD; } + +.epoch-theme-dark .epoch.category20c .bar.category19 { fill: #705CAD; } + +.epoch-theme-dark .epoch.category20c div.ref.category20 { background-color: #52447F; } + +.epoch-theme-dark .epoch.category20c .category20 .line { stroke: #52447F; } + +.epoch-theme-dark .epoch.category20c .category20 .area, .epoch-theme-dark .epoch.category20c .category20 .dot { fill: #52447F; stroke: transparent; } + +.epoch-theme-dark .epoch.category20c .arc.category20 path { fill: #52447F; } + +.epoch-theme-dark .epoch.category20c .bar.category20 { fill: #52447F; } diff --git a/debian/missing-sources/epoch.js b/debian/missing-sources/epoch.js new file mode 100644 index 0000000..92bc514 --- /dev/null +++ b/debian/missing-sources/epoch.js @@ -0,0 +1,3924 @@ +var base, base1, base2, base3; + +if (window.Epoch == null) { + window.Epoch = {}; +} + +if ((base = window.Epoch).Chart == null) { + base.Chart = {}; +} + +if ((base1 = window.Epoch).Time == null) { + base1.Time = {}; +} + +if ((base2 = window.Epoch).Util == null) { + base2.Util = {}; +} + +if ((base3 = window.Epoch).Formats == null) { + base3.Formats = {}; +} + +Epoch.warn = function(msg) { + return (console.warn || console.log)("Epoch Warning: " + msg); +}; + +Epoch.exception = function(msg) { + throw "Epoch Error: " + msg; +}; + +Epoch.TestContext = (function() { + var VOID_METHODS; + + VOID_METHODS = ['arc', 'arcTo', 'beginPath', 'bezierCurveTo', 'clearRect', 'clip', 'closePath', 'drawImage', 'fill', 'fillRect', 'fillText', 'moveTo', 'quadraticCurveTo', 'rect', 'restore', 'rotate', 'save', 'scale', 'scrollPathIntoView', 'setLineDash', 'setTransform', 'stroke', 'strokeRect', 'strokeText', 'transform', 'translate', 'lineTo']; + + function TestContext() { + var i, len, method; + this._log = []; + for (i = 0, len = VOID_METHODS.length; i < len; i++) { + method = VOID_METHODS[i]; + this._makeFauxMethod(method); + } + } + + TestContext.prototype._makeFauxMethod = function(name) { + return this[name] = function() { + var arg; + return this._log.push(name + "(" + (((function() { + var i, len, results; + results = []; + for (i = 0, len = arguments.length; i < len; i++) { + arg = arguments[i]; + results.push(arg.toString()); + } + return results; + }).apply(this, arguments)).join(',')) + ")"); + }; + }; + + TestContext.prototype.getImageData = function() { + var arg; + this._log.push("getImageData(" + (((function() { + var i, len, results; + results = []; + for (i = 0, len = arguments.length; i < len; i++) { + arg = arguments[i]; + results.push(arg.toString()); + } + return results; + }).apply(this, arguments)).join(',')) + ")"); + return { + width: 0, + height: 0, + resolution: 1.0, + data: [] + }; + }; + + return TestContext; + +})(); + +var ref, typeFunction, + hasProp = {}.hasOwnProperty; + +typeFunction = function(objectName) { + return function(v) { + return Object.prototype.toString.call(v) === ("[object " + objectName + "]"); + }; +}; + +Epoch.isArray = (ref = Array.isArray) != null ? ref : typeFunction('Array'); + +Epoch.isObject = typeFunction('Object'); + +Epoch.isString = typeFunction('String'); + +Epoch.isFunction = typeFunction('Function'); + +Epoch.isNumber = typeFunction('Number'); + +Epoch.isElement = function(v) { + if (typeof HTMLElement !== "undefined" && HTMLElement !== null) { + return v instanceof HTMLElement; + } else { + return (v != null) && Epoch.isObject(v) && v.nodeType === 1 && Epoch.isString(v.nodeName); + } +}; + +Epoch.isNonEmptyArray = function(v) { + return Epoch.isArray(v) && v.length > 0; +}; + +Epoch.Util.copy = function(original) { + var copy, k, v; + if (original == null) { + return null; + } + copy = {}; + for (k in original) { + if (!hasProp.call(original, k)) continue; + v = original[k]; + copy[k] = v; + } + return copy; +}; + +Epoch.Util.defaults = function(options, defaults) { + var bothAreObjects, def, k, opt, result, v; + result = Epoch.Util.copy(options); + for (k in defaults) { + if (!hasProp.call(defaults, k)) continue; + v = defaults[k]; + opt = options[k]; + def = defaults[k]; + bothAreObjects = Epoch.isObject(opt) && Epoch.isObject(def); + if ((opt != null) && (def != null)) { + if (bothAreObjects && !Epoch.isArray(opt)) { + result[k] = Epoch.Util.defaults(opt, def); + } else { + result[k] = opt; + } + } else if (opt != null) { + result[k] = opt; + } else { + result[k] = def; + } + } + return result; +}; + +Epoch.Util.formatSI = function(v, fixed, fixIntegers) { + var base, i, label, q, ref1; + if (fixed == null) { + fixed = 1; + } + if (fixIntegers == null) { + fixIntegers = false; + } + if (v < 1000) { + q = v; + if (!((q | 0) === q && !fixIntegers)) { + q = q.toFixed(fixed); + } + return q; + } + ref1 = ['K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; + for (i in ref1) { + if (!hasProp.call(ref1, i)) continue; + label = ref1[i]; + base = Math.pow(10, ((i | 0) + 1) * 3); + if (v >= base && v < Math.pow(10, ((i | 0) + 2) * 3)) { + q = v / base; + if (!((q % 1) === 0 && !fixIntegers)) { + q = q.toFixed(fixed); + } + return q + " " + label; + } + } +}; + +Epoch.Util.formatBytes = function(v, fixed, fix_integers) { + var base, i, label, q, ref1; + if (fixed == null) { + fixed = 1; + } + if (fix_integers == null) { + fix_integers = false; + } + if (v < 1024) { + q = v; + if (!((q % 1) === 0 && !fix_integers)) { + q = q.toFixed(fixed); + } + return q + " B"; + } + ref1 = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + for (i in ref1) { + if (!hasProp.call(ref1, i)) continue; + label = ref1[i]; + base = Math.pow(1024, (i | 0) + 1); + if (v >= base && v < Math.pow(1024, (i | 0) + 2)) { + q = v / base; + if (!((q % 1) === 0 && !fix_integers)) { + q = q.toFixed(fixed); + } + return q + " " + label; + } + } +}; + +Epoch.Util.dasherize = function(str) { + return Epoch.Util.trim(str).replace("\n", '').replace(/\s+/g, '-').toLowerCase(); +}; + +Epoch.Util.domain = function(layers, key) { + var domain, entry, j, l, layer, len, len1, ref1, set; + if (key == null) { + key = 'x'; + } + set = {}; + domain = []; + for (j = 0, len = layers.length; j < len; j++) { + layer = layers[j]; + ref1 = layer.values; + for (l = 0, len1 = ref1.length; l < len1; l++) { + entry = ref1[l]; + if (set[entry[key]] != null) { + continue; + } + domain.push(entry[key]); + set[entry[key]] = true; + } + } + return domain; +}; + +Epoch.Util.trim = function(string) { + if (!Epoch.isString(string)) { + return null; + } + return string.replace(/^\s+/g, '').replace(/\s+$/g, ''); +}; + +Epoch.Util.getComputedStyle = function(element, pseudoElement) { + if (Epoch.isFunction(window.getComputedStyle)) { + return window.getComputedStyle(element, pseudoElement); + } else if (element.currentStyle != null) { + return element.currentStyle; + } +}; + +Epoch.Util.toRGBA = function(color, opacity) { + var all, b, g, parts, r, result, v; + if ((parts = color.match(/^rgba\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*[0-9\.]+\)/))) { + all = parts[0], r = parts[1], g = parts[2], b = parts[3]; + result = "rgba(" + r + "," + g + "," + b + "," + opacity + ")"; + } else if ((v = d3.rgb(color))) { + result = "rgba(" + v.r + "," + v.g + "," + v.b + "," + opacity + ")"; + } + return result; +}; + +Epoch.Util.getContext = function(node, type) { + if (type == null) { + type = '2d'; + } + return node.getContext(type); +}; + +Epoch.Events = (function() { + function Events() { + this._events = {}; + } + + Events.prototype.on = function(name, callback) { + var base1; + if (callback == null) { + return; + } + if ((base1 = this._events)[name] == null) { + base1[name] = []; + } + return this._events[name].push(callback); + }; + + Events.prototype.onAll = function(map) { + var callback, name, results; + if (!Epoch.isObject(map)) { + return; + } + results = []; + for (name in map) { + if (!hasProp.call(map, name)) continue; + callback = map[name]; + results.push(this.on(name, callback)); + } + return results; + }; + + Events.prototype.off = function(name, callback) { + var i, results; + if (!Epoch.isArray(this._events[name])) { + return; + } + if (callback == null) { + return delete this._events[name]; + } + results = []; + while ((i = this._events[name].indexOf(callback)) >= 0) { + results.push(this._events[name].splice(i, 1)); + } + return results; + }; + + Events.prototype.offAll = function(mapOrList) { + var callback, j, len, name, results, results1; + if (Epoch.isArray(mapOrList)) { + results = []; + for (j = 0, len = mapOrList.length; j < len; j++) { + name = mapOrList[j]; + results.push(this.off(name)); + } + return results; + } else if (Epoch.isObject(mapOrList)) { + results1 = []; + for (name in mapOrList) { + if (!hasProp.call(mapOrList, name)) continue; + callback = mapOrList[name]; + results1.push(this.off(name, callback)); + } + return results1; + } + }; + + Events.prototype.trigger = function(name) { + var args, callback, fn, i, j, len, ref1, results; + if (this._events[name] == null) { + return; + } + args = (function() { + var j, ref1, results; + results = []; + for (i = j = 1, ref1 = arguments.length; 1 <= ref1 ? j < ref1 : j > ref1; i = 1 <= ref1 ? ++j : --j) { + results.push(arguments[i]); + } + return results; + }).apply(this, arguments); + ref1 = this._events[name]; + results = []; + for (j = 0, len = ref1.length; j < len; j++) { + callback = ref1[j]; + fn = null; + if (Epoch.isString(callback)) { + fn = this[callback]; + } else if (Epoch.isFunction(callback)) { + fn = callback; + } + if (fn == null) { + Epoch.exception("Callback for event '" + name + "' is not a function or reference to a method."); + } + results.push(fn.apply(this, args)); + } + return results; + }; + + return Events; + +})(); + +Epoch.Util.flatten = function(multiarray) { + var array, item, j, l, len, len1, result; + if (!Array.isArray(multiarray)) { + throw new Error('Epoch.Util.flatten only accepts arrays'); + } + result = []; + for (j = 0, len = multiarray.length; j < len; j++) { + array = multiarray[j]; + if (Array.isArray(array)) { + for (l = 0, len1 = array.length; l < len1; l++) { + item = array[l]; + result.push(item); + } + } else { + result.push(array); + } + } + return result; +}; + +d3.selection.prototype.width = function(value) { + if ((value != null) && Epoch.isString(value)) { + return this.style('width', value); + } else if ((value != null) && Epoch.isNumber(value)) { + return this.style('width', value + "px"); + } else { + return +Epoch.Util.getComputedStyle(this.node(), null).width.replace('px', ''); + } +}; + +d3.selection.prototype.height = function(value) { + if ((value != null) && Epoch.isString(value)) { + return this.style('height', value); + } else if ((value != null) && Epoch.isNumber(value)) { + return this.style('height', value + "px"); + } else { + return +Epoch.Util.getComputedStyle(this.node(), null).height.replace('px', ''); + } +}; + +var d3Seconds; + +Epoch.Formats.regular = function(d) { + return d; +}; + +Epoch.Formats.si = function(d) { + return Epoch.Util.formatSI(d); +}; + +Epoch.Formats.percent = function(d) { + return (d * 100).toFixed(1) + "%"; +}; + +Epoch.Formats.seconds = function(t) { + return d3Seconds(new Date(t * 1000)); +}; + +d3Seconds = d3.time.format('%I:%M:%S %p'); + +Epoch.Formats.bytes = function(d) { + return Epoch.Util.formatBytes(d); +}; + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Chart.Base = (function(superClass) { + var defaults, optionListeners; + + extend(Base, superClass); + + defaults = { + width: 320, + height: 240, + dataFormat: null + }; + + optionListeners = { + 'option:width': 'dimensionsChanged', + 'option:height': 'dimensionsChanged', + 'layer:shown': 'layerChanged', + 'layer:hidden': 'layerChanged' + }; + + function Base(options1) { + this.options = options1 != null ? options1 : {}; + Base.__super__.constructor.call(this); + if (this.options.model) { + if (this.options.model.hasData() != null) { + this.setData(this.options.model.getData(this.options.type, this.options.dataFormat)); + } else { + this.setData(this.options.data || []); + } + this.options.model.on('data:updated', (function(_this) { + return function() { + return _this.setDataFromModel(); + }; + })(this)); + } else { + this.setData(this.options.data || []); + } + if (this.options.el != null) { + this.el = d3.select(this.options.el); + } + this.width = this.options.width; + this.height = this.options.height; + if (this.el != null) { + if (this.width == null) { + this.width = this.el.width(); + } + if (this.height == null) { + this.height = this.el.height(); + } + } else { + if (this.width == null) { + this.width = defaults.width; + } + if (this.height == null) { + this.height = defaults.height; + } + this.el = d3.select(document.createElement('DIV')).attr('width', this.width).attr('height', this.height); + } + this.onAll(optionListeners); + } + + Base.prototype._getAllOptions = function() { + return Epoch.Util.defaults({}, this.options); + }; + + Base.prototype._getOption = function(key) { + var parts, scope, subkey; + parts = key.split('.'); + scope = this.options; + while (parts.length && (scope != null)) { + subkey = parts.shift(); + scope = scope[subkey]; + } + return scope; + }; + + Base.prototype._setOption = function(key, value) { + var parts, scope, subkey; + parts = key.split('.'); + scope = this.options; + while (parts.length) { + subkey = parts.shift(); + if (parts.length === 0) { + scope[subkey] = arguments[1]; + this.trigger("option:" + arguments[0]); + return; + } + if (scope[subkey] == null) { + scope[subkey] = {}; + } + scope = scope[subkey]; + } + }; + + Base.prototype._setManyOptions = function(options, prefix) { + var key, results, value; + if (prefix == null) { + prefix = ''; + } + results = []; + for (key in options) { + if (!hasProp.call(options, key)) continue; + value = options[key]; + if (Epoch.isObject(value)) { + results.push(this._setManyOptions(value, (prefix + key) + ".")); + } else { + results.push(this._setOption(prefix + key, value)); + } + } + return results; + }; + + Base.prototype.option = function() { + if (arguments.length === 0) { + return this._getAllOptions(); + } else if (arguments.length === 1 && Epoch.isString(arguments[0])) { + return this._getOption(arguments[0]); + } else if (arguments.length === 2 && Epoch.isString(arguments[0])) { + return this._setOption(arguments[0], arguments[1]); + } else if (arguments.length === 1 && Epoch.isObject(arguments[0])) { + return this._setManyOptions(arguments[0]); + } + }; + + Base.prototype.setDataFromModel = function() { + var prepared; + prepared = this._prepareData(this.options.model.getData(this.options.type, this.options.dataFormat)); + this.data = this._annotateLayers(prepared); + return this.draw(); + }; + + Base.prototype.setData = function(data, options) { + var prepared; + if (options == null) { + options = {}; + } + prepared = this._prepareData((this.rawData = this._formatData(data))); + return this.data = this._annotateLayers(prepared); + }; + + Base.prototype._prepareData = function(data) { + return data; + }; + + Base.prototype._formatData = function(data) { + return Epoch.Data.formatData(data, this.options.type, this.options.dataFormat); + }; + + Base.prototype._annotateLayers = function(data) { + var category, classes, i, layer, len; + category = 1; + for (i = 0, len = data.length; i < len; i++) { + layer = data[i]; + classes = ['layer']; + classes.push("category" + category); + layer.category = category; + layer.visible = true; + if (layer.label != null) { + classes.push(Epoch.Util.dasherize(layer.label)); + } + layer.className = classes.join(' '); + category++; + } + return data; + }; + + Base.prototype._findLayer = function(labelOrIndex) { + var i, index, l, layer, len, ref; + layer = null; + if (Epoch.isString(labelOrIndex)) { + ref = this.data; + for (i = 0, len = ref.length; i < len; i++) { + l = ref[i]; + if (l.label === labelOrIndex) { + layer = l; + break; + } + } + } else if (Epoch.isNumber(labelOrIndex)) { + index = parseInt(labelOrIndex); + if (!(index < 0 || index >= this.data.length)) { + layer = this.data[index]; + } + } + return layer; + }; + + Base.prototype.showLayer = function(labelOrIndex) { + var layer; + if (!(layer = this._findLayer(labelOrIndex))) { + return; + } + if (layer.visible) { + return; + } + layer.visible = true; + return this.trigger('layer:shown'); + }; + + Base.prototype.hideLayer = function(labelOrIndex) { + var layer; + if (!(layer = this._findLayer(labelOrIndex))) { + return; + } + if (!layer.visible) { + return; + } + layer.visible = false; + return this.trigger('layer:hidden'); + }; + + Base.prototype.toggleLayer = function(labelOrIndex) { + var layer; + if (!(layer = this._findLayer(labelOrIndex))) { + return; + } + layer.visible = !layer.visible; + if (layer.visible) { + return this.trigger('layer:shown'); + } else { + return this.trigger('layer:hidden'); + } + }; + + Base.prototype.isLayerVisible = function(labelOrIndex) { + var layer; + if (!(layer = this._findLayer(labelOrIndex))) { + return null; + } + return layer.visible; + }; + + Base.prototype.getVisibleLayers = function() { + return this.data.filter(function(layer) { + return layer.visible; + }); + }; + + Base.prototype.update = function(data, draw) { + if (draw == null) { + draw = true; + } + this.setData(data); + if (draw) { + return this.draw(); + } + }; + + Base.prototype.draw = function() { + return this.trigger('draw'); + }; + + Base.prototype._getScaleDomain = function(givenDomain) { + var layers, maxFn, minFn, values; + if (Array.isArray(givenDomain)) { + return givenDomain; + } + if (Epoch.isString(givenDomain)) { + layers = this.getVisibleLayers().filter(function(l) { + return l.range === givenDomain; + }).map(function(l) { + return l.values; + }); + if ((layers != null) && layers.length) { + values = Epoch.Util.flatten(layers).map(function(d) { + return d.y; + }); + minFn = function(memo, curr) { + if (curr < memo) { + return curr; + } else { + return memo; + } + }; + maxFn = function(memo, curr) { + if (curr > memo) { + return curr; + } else { + return memo; + } + }; + return [values.reduce(minFn, values[0]), values.reduce(maxFn, values[0])]; + } + } + if (Array.isArray(this.options.range)) { + return this.options.range; + } else if (this.options.range && Array.isArray(this.options.range.left)) { + return this.options.range.left; + } else if (this.options.range && Array.isArray(this.options.range.right)) { + return this.options.range.right; + } else { + return this.extent(function(d) { + return d.y; + }); + } + }; + + Base.prototype.extent = function(cmp) { + return [ + d3.min(this.getVisibleLayers(), function(layer) { + return d3.min(layer.values, cmp); + }), d3.max(this.getVisibleLayers(), function(layer) { + return d3.max(layer.values, cmp); + }) + ]; + }; + + Base.prototype.dimensionsChanged = function() { + this.width = this.option('width') || this.width; + this.height = this.option('height') || this.height; + this.el.width(this.width); + return this.el.height(this.height); + }; + + Base.prototype.layerChanged = function() { + return this.draw(); + }; + + return Base; + +})(Epoch.Events); + +Epoch.Chart.SVG = (function(superClass) { + extend(SVG, superClass); + + function SVG(options1) { + this.options = options1 != null ? options1 : {}; + SVG.__super__.constructor.call(this, this.options); + if (this.el != null) { + this.svg = this.el.append('svg'); + } else { + this.svg = d3.select(document.createElement('svg')); + } + this.svg.attr({ + xmlns: 'http://www.w3.org/2000/svg', + width: this.width, + height: this.height + }); + } + + SVG.prototype.dimensionsChanged = function() { + SVG.__super__.dimensionsChanged.call(this); + return this.svg.attr('width', this.width).attr('height', this.height); + }; + + return SVG; + +})(Epoch.Chart.Base); + +Epoch.Chart.Canvas = (function(superClass) { + extend(Canvas, superClass); + + function Canvas(options1) { + this.options = options1 != null ? options1 : {}; + Canvas.__super__.constructor.call(this, this.options); + if (this.options.pixelRatio != null) { + this.pixelRatio = this.options.pixelRatio; + } else if (window.devicePixelRatio != null) { + this.pixelRatio = window.devicePixelRatio; + } else { + this.pixelRatio = 1; + } + this.canvas = d3.select(document.createElement('CANVAS')); + this.canvas.style({ + 'width': this.width + "px", + 'height': this.height + "px" + }); + this.canvas.attr({ + width: this.getWidth(), + height: this.getHeight() + }); + if (this.el != null) { + this.el.node().appendChild(this.canvas.node()); + } + this.ctx = Epoch.Util.getContext(this.canvas.node()); + } + + Canvas.prototype.getWidth = function() { + return this.width * this.pixelRatio; + }; + + Canvas.prototype.getHeight = function() { + return this.height * this.pixelRatio; + }; + + Canvas.prototype.clear = function() { + return this.ctx.clearRect(0, 0, this.getWidth(), this.getHeight()); + }; + + Canvas.prototype.getStyles = function(selector) { + return Epoch.QueryCSS.getStyles(selector, this.el); + }; + + Canvas.prototype.dimensionsChanged = function() { + Canvas.__super__.dimensionsChanged.call(this); + this.canvas.style({ + 'width': this.width + "px", + 'height': this.height + "px" + }); + return this.canvas.attr({ + width: this.getWidth(), + height: this.getHeight() + }); + }; + + Canvas.prototype.redraw = function() { + Epoch.QueryCSS.purge(); + return this.draw(); + }; + + return Canvas; + +})(Epoch.Chart.Base); + +var QueryCSS; + +QueryCSS = (function() { + var CONTAINER_HASH_ATTR, PUT_EXPR, REFERENCE_CONTAINER_ID, containerCount, logging, nextContainerId, put; + + function QueryCSS() {} + + REFERENCE_CONTAINER_ID = '_canvas_css_reference'; + + CONTAINER_HASH_ATTR = 'data-epoch-container-id'; + + containerCount = 0; + + nextContainerId = function() { + return "epoch-container-" + (containerCount++); + }; + + PUT_EXPR = /^([^#. ]+)?(#[^. ]+)?(\.[^# ]+)?$/; + + logging = false; + + put = function(selector) { + var classNames, element, id, match, tag, whole; + match = selector.match(PUT_EXPR); + if (match == null) { + return Epoch.error('Query CSS cannot match given selector: ' + selector); + } + whole = match[0], tag = match[1], id = match[2], classNames = match[3]; + tag = (tag != null ? tag : 'div').toUpperCase(); + element = document.createElement(tag); + if (id != null) { + element.id = id.substr(1); + } + if (classNames != null) { + element.className = classNames.substr(1).replace(/\./g, ' '); + } + return element; + }; + + QueryCSS.log = function(b) { + return logging = b; + }; + + QueryCSS.cache = {}; + + QueryCSS.styleList = ['fill', 'stroke', 'stroke-width']; + + QueryCSS.container = null; + + QueryCSS.purge = function() { + return QueryCSS.cache = {}; + }; + + QueryCSS.getContainer = function() { + var container; + if (QueryCSS.container != null) { + return QueryCSS.container; + } + container = document.createElement('DIV'); + container.id = REFERENCE_CONTAINER_ID; + document.body.appendChild(container); + return QueryCSS.container = d3.select(container); + }; + + QueryCSS.hash = function(selector, container) { + var containerId; + containerId = container.attr(CONTAINER_HASH_ATTR); + if (containerId == null) { + containerId = nextContainerId(); + container.attr(CONTAINER_HASH_ATTR, containerId); + } + return containerId + "__" + selector; + }; + + QueryCSS.getStyles = function(selector, container) { + var cache, cacheKey, el, element, i, j, k, len, len1, len2, name, parent, parentNode, parents, ref, ref1, ref2, root, sel, selectorList, styles, subSelector; + cacheKey = QueryCSS.hash(selector, container); + cache = QueryCSS.cache[cacheKey]; + if (cache != null) { + return cache; + } + parents = []; + parentNode = container.node().parentNode; + while ((parentNode != null) && parentNode.nodeName.toLowerCase() !== 'body') { + parents.unshift(parentNode); + parentNode = parentNode.parentNode; + } + parents.push(container.node()); + selectorList = []; + for (i = 0, len = parents.length; i < len; i++) { + element = parents[i]; + sel = element.nodeName.toLowerCase(); + if ((element.id != null) && element.id.length > 0) { + sel += '#' + element.id; + } + if ((element.className != null) && element.className.length > 0) { + sel += '.' + Epoch.Util.trim(element.className).replace(/\s+/g, '.'); + } + selectorList.push(sel); + } + selectorList.push('svg'); + ref1 = Epoch.Util.trim(selector).split(/\s+/); + for (j = 0, len1 = ref1.length; j < len1; j++) { + subSelector = ref1[j]; + selectorList.push(subSelector); + } + if (logging) { + console.log(selectorList); + } + parent = root = put(selectorList.shift()); + while (selectorList.length) { + el = put(selectorList.shift()); + parent.appendChild(el); + parent = el; + } + if (logging) { + console.log(root); + } + QueryCSS.getContainer().node().appendChild(root); + ref = d3.select('#' + REFERENCE_CONTAINER_ID + ' ' + selector); + styles = {}; + ref2 = QueryCSS.styleList; + for (k = 0, len2 = ref2.length; k < len2; k++) { + name = ref2[k]; + styles[name] = ref.style(name); + } + QueryCSS.cache[cacheKey] = styles; + QueryCSS.getContainer().html(''); + return styles; + }; + + return QueryCSS; + +})(); + +Epoch.QueryCSS = QueryCSS; + +var applyLayerLabel, base, + hasProp = {}.hasOwnProperty, + slice = [].slice; + +if (Epoch.Data == null) { + Epoch.Data = {}; +} + +if ((base = Epoch.Data).Format == null) { + base.Format = {}; +} + +applyLayerLabel = function(layer, options, i, keys) { + var autoLabels, keyLabels, label, labels, ref; + if (keys == null) { + keys = []; + } + ref = [options.labels, options.autoLabels, options.keyLabels], labels = ref[0], autoLabels = ref[1], keyLabels = ref[2]; + if ((labels != null) && Epoch.isArray(labels) && labels.length > i) { + layer.label = labels[i]; + } else if (keyLabels && keys.length > i) { + layer.label = keys[i]; + } else if (autoLabels) { + label = []; + while (i >= 0) { + label.push(String.fromCharCode(65 + (i % 26))); + i -= 26; + } + layer.label = label.join(''); + } + return layer; +}; + +Epoch.Data.Format.array = (function() { + var buildLayers, defaultOptions, format, formatBasicPlot, formatHeatmap, formatPie, formatTimePlot; + defaultOptions = { + x: function(d, i) { + return i; + }, + y: function(d, i) { + return d; + }, + time: function(d, i, startTime) { + return parseInt(startTime) + parseInt(i); + }, + type: 'area', + autoLabels: false, + labels: [], + startTime: parseInt(new Date().getTime() / 1000) + }; + buildLayers = function(data, options, mapFn) { + var i, result, series; + result = []; + if (Epoch.isArray(data[0])) { + for (i in data) { + if (!hasProp.call(data, i)) continue; + series = data[i]; + result.push(applyLayerLabel({ + values: series.map(mapFn) + }, options, parseInt(i))); + } + } else { + result.push(applyLayerLabel({ + values: data.map(mapFn) + }, options, 0)); + } + return result; + }; + formatBasicPlot = function(data, options) { + return buildLayers(data, options, function(d, i) { + return { + x: options.x(d, i), + y: options.y(d, i) + }; + }); + }; + formatTimePlot = function(data, options) { + return buildLayers(data, options, function(d, i) { + return { + time: options.time(d, i, options.startTime), + y: options.y(d, i) + }; + }); + }; + formatHeatmap = function(data, options) { + return buildLayers(data, options, function(d, i) { + return { + time: options.time(d, i, options.startTime), + histogram: d + }; + }); + }; + formatPie = function(data, options) { + var i, result, v; + result = []; + for (i in data) { + if (!hasProp.call(data, i)) continue; + v = data[i]; + if (!Epoch.isNumber(data[0])) { + return []; + } + result.push(applyLayerLabel({ + value: v + }, options, i)); + } + return result; + }; + format = function(data, options) { + var opt; + if (data == null) { + data = []; + } + if (options == null) { + options = {}; + } + if (!Epoch.isNonEmptyArray(data)) { + return []; + } + opt = Epoch.Util.defaults(options, defaultOptions); + if (opt.type === 'time.heatmap') { + return formatHeatmap(data, opt); + } else if (opt.type.match(/^time\./)) { + return formatTimePlot(data, opt); + } else if (opt.type === 'pie') { + return formatPie(data, opt); + } else { + return formatBasicPlot(data, opt); + } + }; + format.entry = function(datum, options) { + var d, data, k, layer, len, opt, ref, results; + if (options == null) { + options = {}; + } + if (options.type === 'time.gauge') { + if (datum == null) { + return 0; + } + opt = Epoch.Util.defaults(options, defaultOptions); + d = Epoch.isArray(datum) ? datum[0] : datum; + return opt.y(d, 0); + } + if (datum == null) { + return []; + } + if (options.startTime == null) { + options.startTime = parseInt(new Date().getTime() / 1000); + } + if (Epoch.isArray(datum)) { + data = datum.map(function(d) { + return [d]; + }); + } else { + data = [datum]; + } + ref = format(data, options); + results = []; + for (k = 0, len = ref.length; k < len; k++) { + layer = ref[k]; + results.push(layer.values[0]); + } + return results; + }; + return format; +})(); + +Epoch.Data.Format.tuple = (function() { + var buildLayers, defaultOptions, format; + defaultOptions = { + x: function(d, i) { + return d; + }, + y: function(d, i) { + return d; + }, + time: function(d, i) { + return d; + }, + type: 'area', + autoLabels: false, + labels: [] + }; + buildLayers = function(data, options, mapFn) { + var i, result, series; + if (!Epoch.isArray(data[0])) { + return []; + } + result = []; + if (Epoch.isArray(data[0][0])) { + for (i in data) { + if (!hasProp.call(data, i)) continue; + series = data[i]; + result.push(applyLayerLabel({ + values: series.map(mapFn) + }, options, parseInt(i))); + } + } else { + result.push(applyLayerLabel({ + values: data.map(mapFn) + }, options, 0)); + } + return result; + }; + format = function(data, options) { + var opt; + if (data == null) { + data = []; + } + if (options == null) { + options = {}; + } + if (!Epoch.isNonEmptyArray(data)) { + return []; + } + opt = Epoch.Util.defaults(options, defaultOptions); + if (opt.type === 'pie' || opt.type === 'time.heatmap' || opt.type === 'time.gauge') { + return []; + } else if (opt.type.match(/^time\./)) { + return buildLayers(data, opt, function(d, i) { + return { + time: opt.time(d[0], parseInt(i)), + y: opt.y(d[1], parseInt(i)) + }; + }); + } else { + return buildLayers(data, opt, function(d, i) { + return { + x: opt.x(d[0], parseInt(i)), + y: opt.y(d[1], parseInt(i)) + }; + }); + } + }; + format.entry = function(datum, options) { + var data, k, layer, len, ref, results; + if (options == null) { + options = {}; + } + if (datum == null) { + return []; + } + if (options.startTime == null) { + options.startTime = parseInt(new Date().getTime() / 1000); + } + if (Epoch.isArray(datum) && Epoch.isArray(datum[0])) { + data = datum.map(function(d) { + return [d]; + }); + } else { + data = [datum]; + } + ref = format(data, options); + results = []; + for (k = 0, len = ref.length; k < len; k++) { + layer = ref[k]; + results.push(layer.values[0]); + } + return results; + }; + return format; +})(); + +Epoch.Data.Format.keyvalue = (function() { + var buildLayers, defaultOptions, format, formatBasicPlot, formatTimePlot; + defaultOptions = { + type: 'area', + x: function(d, i) { + return parseInt(i); + }, + y: function(d, i) { + return d; + }, + time: function(d, i, startTime) { + return parseInt(startTime) + parseInt(i); + }, + labels: [], + autoLabels: false, + keyLabels: true, + startTime: parseInt(new Date().getTime() / 1000) + }; + buildLayers = function(data, keys, options, mapFn) { + var d, i, j, key, result, values; + result = []; + for (j in keys) { + if (!hasProp.call(keys, j)) continue; + key = keys[j]; + values = []; + for (i in data) { + if (!hasProp.call(data, i)) continue; + d = data[i]; + values.push(mapFn(d, key, parseInt(i))); + } + result.push(applyLayerLabel({ + values: values + }, options, parseInt(j), keys)); + } + return result; + }; + formatBasicPlot = function(data, keys, options) { + return buildLayers(data, keys, options, function(d, key, i) { + var x; + if (Epoch.isString(options.x)) { + x = d[options.x]; + } else { + x = options.x(d, parseInt(i)); + } + return { + x: x, + y: options.y(d[key], parseInt(i)) + }; + }); + }; + formatTimePlot = function(data, keys, options, rangeName) { + if (rangeName == null) { + rangeName = 'y'; + } + return buildLayers(data, keys, options, function(d, key, i) { + var value; + if (Epoch.isString(options.time)) { + value = { + time: d[options.time] + }; + } else { + value = { + time: options.time(d, parseInt(i), options.startTime) + }; + } + value[rangeName] = options.y(d[key], parseInt(i)); + return value; + }); + }; + format = function(data, keys, options) { + var opt; + if (data == null) { + data = []; + } + if (keys == null) { + keys = []; + } + if (options == null) { + options = {}; + } + if (!(Epoch.isNonEmptyArray(data) && Epoch.isNonEmptyArray(keys))) { + return []; + } + opt = Epoch.Util.defaults(options, defaultOptions); + if (opt.type === 'pie' || opt.type === 'time.gauge') { + return []; + } else if (opt.type === 'time.heatmap') { + return formatTimePlot(data, keys, opt, 'histogram'); + } else if (opt.type.match(/^time\./)) { + return formatTimePlot(data, keys, opt); + } else { + return formatBasicPlot(data, keys, opt); + } + }; + format.entry = function(datum, keys, options) { + var k, layer, len, ref, results; + if (keys == null) { + keys = []; + } + if (options == null) { + options = {}; + } + if (!((datum != null) && Epoch.isNonEmptyArray(keys))) { + return []; + } + if (options.startTime == null) { + options.startTime = parseInt(new Date().getTime() / 1000); + } + ref = format([datum], keys, options); + results = []; + for (k = 0, len = ref.length; k < len; k++) { + layer = ref[k]; + results.push(layer.values[0]); + } + return results; + }; + return format; +})(); + +Epoch.data = function() { + var args, formatFn, formatter; + formatter = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + if ((formatFn = Epoch.Data.Format[formatter]) == null) { + return []; + } + return formatFn.apply(formatFn, args); +}; + +Epoch.Data.formatData = function(data, type, dataFormat) { + var a, args, k, len, opts, ref; + if (data == null) { + data = []; + } + if (!Epoch.isNonEmptyArray(data)) { + return data; + } + if (Epoch.isString(dataFormat)) { + opts = { + type: type + }; + return Epoch.data(dataFormat, data, opts); + } + if (!Epoch.isObject(dataFormat)) { + return data; + } + if (!((dataFormat.name != null) && Epoch.isString(dataFormat.name))) { + return data; + } + if (Epoch.Data.Format[dataFormat.name] == null) { + return data; + } + args = [dataFormat.name, data]; + if ((dataFormat["arguments"] != null) && Epoch.isArray(dataFormat["arguments"])) { + ref = dataFormat["arguments"]; + for (k = 0, len = ref.length; k < len; k++) { + a = ref[k]; + args.push(a); + } + } + if (dataFormat.options != null) { + opts = dataFormat.options; + if (type != null) { + if (opts.type == null) { + opts.type = type; + } + } + args.push(opts); + } else if (type != null) { + args.push({ + type: type + }); + } + return Epoch.data.apply(Epoch.data, args); +}; + +Epoch.Data.formatEntry = function(datum, type, format) { + var a, args, dataFormat, entry, k, len, opts, ref; + if (format == null) { + return datum; + } + if (Epoch.isString(format)) { + opts = { + type: type + }; + return Epoch.Data.Format[format].entry(datum, opts); + } + if (!Epoch.isObject(format)) { + return datum; + } + if (!((format.name != null) && Epoch.isString(format.name))) { + return datum; + } + if (Epoch.Data.Format[format.name] == null) { + return datum; + } + dataFormat = Epoch.Util.defaults(format, {}); + args = [datum]; + if ((dataFormat["arguments"] != null) && Epoch.isArray(dataFormat["arguments"])) { + ref = dataFormat["arguments"]; + for (k = 0, len = ref.length; k < len; k++) { + a = ref[k]; + args.push(a); + } + } + if (dataFormat.options != null) { + opts = dataFormat.options; + opts.type = type; + args.push(opts); + } else if (type != null) { + args.push({ + type: type + }); + } + entry = Epoch.Data.Format[dataFormat.name].entry; + return entry.apply(entry, args); +}; + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Model = (function(superClass) { + var defaults; + + extend(Model, superClass); + + defaults = { + dataFormat: null + }; + + function Model(options) { + if (options == null) { + options = {}; + } + Model.__super__.constructor.call(this); + options = Epoch.Util.defaults(options, defaults); + this.dataFormat = options.dataFormat; + this.data = options.data; + this.loading = false; + } + + Model.prototype.setData = function(data) { + this.data = data; + return this.trigger('data:updated'); + }; + + Model.prototype.push = function(entry) { + this.entry = entry; + return this.trigger('data:push'); + }; + + Model.prototype.hasData = function() { + return this.data != null; + }; + + Model.prototype.getData = function(type, dataFormat) { + if (dataFormat == null) { + dataFormat = this.dataFormat; + } + return Epoch.Data.formatData(this.data, type, dataFormat); + }; + + Model.prototype.getNext = function(type, dataFormat) { + if (dataFormat == null) { + dataFormat = this.dataFormat; + } + return Epoch.Data.formatEntry(this.entry, type, dataFormat); + }; + + return Model; + +})(Epoch.Events); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Chart.Plot = (function(superClass) { + var defaultAxisMargins, defaults, optionListeners; + + extend(Plot, superClass); + + defaults = { + domain: null, + range: null, + axes: ['left', 'bottom'], + ticks: { + top: 14, + bottom: 14, + left: 5, + right: 5 + }, + tickFormats: { + top: Epoch.Formats.regular, + bottom: Epoch.Formats.regular, + left: Epoch.Formats.si, + right: Epoch.Formats.si + } + }; + + defaultAxisMargins = { + top: 25, + right: 50, + bottom: 25, + left: 50 + }; + + optionListeners = { + 'option:margins.top': 'marginsChanged', + 'option:margins.right': 'marginsChanged', + 'option:margins.bottom': 'marginsChanged', + 'option:margins.left': 'marginsChanged', + 'option:axes': 'axesChanged', + 'option:ticks.top': 'ticksChanged', + 'option:ticks.right': 'ticksChanged', + 'option:ticks.bottom': 'ticksChanged', + 'option:ticks.left': 'ticksChanged', + 'option:tickFormats.top': 'tickFormatsChanged', + 'option:tickFormats.right': 'tickFormatsChanged', + 'option:tickFormats.bottom': 'tickFormatsChanged', + 'option:tickFormats.left': 'tickFormatsChanged', + 'option:domain': 'domainChanged', + 'option:range': 'rangeChanged' + }; + + function Plot(options) { + var givenMargins, i, len, pos, ref; + this.options = options != null ? options : {}; + givenMargins = Epoch.Util.copy(this.options.margins) || {}; + Plot.__super__.constructor.call(this, this.options = Epoch.Util.defaults(this.options, defaults)); + this.margins = {}; + ref = ['top', 'right', 'bottom', 'left']; + for (i = 0, len = ref.length; i < len; i++) { + pos = ref[i]; + this.margins[pos] = (this.options.margins != null) && (this.options.margins[pos] != null) ? this.options.margins[pos] : this.hasAxis(pos) ? defaultAxisMargins[pos] : 6; + } + this.g = this.svg.append("g").attr("transform", "translate(" + this.margins.left + ", " + this.margins.top + ")"); + this.onAll(optionListeners); + } + + Plot.prototype.setTickFormat = function(axis, fn) { + return this.options.tickFormats[axis] = fn; + }; + + Plot.prototype.hasAxis = function(axis) { + return this.options.axes.indexOf(axis) > -1; + }; + + Plot.prototype.innerWidth = function() { + return this.width - (this.margins.left + this.margins.right); + }; + + Plot.prototype.innerHeight = function() { + return this.height - (this.margins.top + this.margins.bottom); + }; + + Plot.prototype.x = function() { + var domain, ref; + domain = (ref = this.options.domain) != null ? ref : this.extent(function(d) { + return d.x; + }); + return d3.scale.linear().domain(domain).range([0, this.innerWidth()]); + }; + + Plot.prototype.y = function(givenDomain) { + return d3.scale.linear().domain(this._getScaleDomain(givenDomain)).range([this.innerHeight(), 0]); + }; + + Plot.prototype.bottomAxis = function() { + return d3.svg.axis().scale(this.x()).orient('bottom').ticks(this.options.ticks.bottom).tickFormat(this.options.tickFormats.bottom); + }; + + Plot.prototype.topAxis = function() { + return d3.svg.axis().scale(this.x()).orient('top').ticks(this.options.ticks.top).tickFormat(this.options.tickFormats.top); + }; + + Plot.prototype.leftAxis = function() { + var range; + range = this.options.range ? this.options.range.left : null; + return d3.svg.axis().scale(this.y(range)).orient('left').ticks(this.options.ticks.left).tickFormat(this.options.tickFormats.left); + }; + + Plot.prototype.rightAxis = function() { + var range; + range = this.options.range ? this.options.range.right : null; + return d3.svg.axis().scale(this.y(range)).orient('right').ticks(this.options.ticks.right).tickFormat(this.options.tickFormats.right); + }; + + Plot.prototype.draw = function() { + if (this._axesDrawn) { + this._redrawAxes(); + } else { + this._drawAxes(); + } + return Plot.__super__.draw.call(this); + }; + + Plot.prototype._redrawAxes = function() { + if (this.hasAxis('bottom')) { + this.g.selectAll('.x.axis.bottom').transition().duration(500).ease('linear').call(this.bottomAxis()); + } + if (this.hasAxis('top')) { + this.g.selectAll('.x.axis.top').transition().duration(500).ease('linear').call(this.topAxis()); + } + if (this.hasAxis('left')) { + this.g.selectAll('.y.axis.left').transition().duration(500).ease('linear').call(this.leftAxis()); + } + if (this.hasAxis('right')) { + return this.g.selectAll('.y.axis.right').transition().duration(500).ease('linear').call(this.rightAxis()); + } + }; + + Plot.prototype._drawAxes = function() { + if (this.hasAxis('bottom')) { + this.g.append("g").attr("class", "x axis bottom").attr("transform", "translate(0, " + (this.innerHeight()) + ")").call(this.bottomAxis()); + } + if (this.hasAxis('top')) { + this.g.append("g").attr('class', 'x axis top').call(this.topAxis()); + } + if (this.hasAxis('left')) { + this.g.append("g").attr("class", "y axis left").call(this.leftAxis()); + } + if (this.hasAxis('right')) { + this.g.append('g').attr('class', 'y axis right').attr('transform', "translate(" + (this.innerWidth()) + ", 0)").call(this.rightAxis()); + } + return this._axesDrawn = true; + }; + + Plot.prototype.dimensionsChanged = function() { + Plot.__super__.dimensionsChanged.call(this); + this.g.selectAll('.axis').remove(); + this._axesDrawn = false; + return this.draw(); + }; + + Plot.prototype.marginsChanged = function() { + var pos, ref, size; + if (this.options.margins == null) { + return; + } + ref = this.options.margins; + for (pos in ref) { + if (!hasProp.call(ref, pos)) continue; + size = ref[pos]; + if (size == null) { + this.margins[pos] = 6; + } else { + this.margins[pos] = size; + } + } + this.g.transition().duration(750).attr("transform", "translate(" + this.margins.left + ", " + this.margins.top + ")"); + return this.draw(); + }; + + Plot.prototype.axesChanged = function() { + var i, len, pos, ref; + ref = ['top', 'right', 'bottom', 'left']; + for (i = 0, len = ref.length; i < len; i++) { + pos = ref[i]; + if ((this.options.margins != null) && (this.options.margins[pos] != null)) { + continue; + } + if (this.hasAxis(pos)) { + this.margins[pos] = defaultAxisMargins[pos]; + } else { + this.margins[pos] = 6; + } + } + this.g.transition().duration(750).attr("transform", "translate(" + this.margins.left + ", " + this.margins.top + ")"); + this.g.selectAll('.axis').remove(); + this._axesDrawn = false; + return this.draw(); + }; + + Plot.prototype.ticksChanged = function() { + return this.draw(); + }; + + Plot.prototype.tickFormatsChanged = function() { + return this.draw(); + }; + + Plot.prototype.domainChanged = function() { + return this.draw(); + }; + + Plot.prototype.rangeChanged = function() { + return this.draw(); + }; + + return Plot; + +})(Epoch.Chart.SVG); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Chart.Area = (function(superClass) { + extend(Area, superClass); + + function Area(options) { + var base; + this.options = options != null ? options : {}; + if ((base = this.options).type == null) { + base.type = 'area'; + } + Area.__super__.constructor.call(this, this.options); + this.draw(); + } + + Area.prototype.y = function() { + var a, i, k, layer, len, ref, ref1, ref2, v; + a = []; + ref = this.getVisibleLayers(); + for (i = 0, len = ref.length; i < len; i++) { + layer = ref[i]; + ref1 = layer.values; + for (k in ref1) { + if (!hasProp.call(ref1, k)) continue; + v = ref1[k]; + if (a[k] != null) { + a[k] += v.y; + } + if (a[k] == null) { + a[k] = v.y; + } + } + } + return d3.scale.linear().domain((ref2 = this.options.range) != null ? ref2 : [0, d3.max(a)]).range([this.height - this.margins.top - this.margins.bottom, 0]); + }; + + Area.prototype.draw = function() { + var area, data, layer, layers, ref, stack, x, y; + ref = [this.x(), this.y(), this.getVisibleLayers()], x = ref[0], y = ref[1], layers = ref[2]; + this.g.selectAll('.layer').remove(); + if (layers.length === 0) { + return; + } + area = d3.svg.area().x(function(d) { + return x(d.x); + }).y0(function(d) { + return y(d.y0); + }).y1(function(d) { + return y(d.y0 + d.y); + }); + stack = d3.layout.stack().values(function(d) { + return d.values; + }); + data = stack(layers); + layer = this.g.selectAll('.layer').data(layers, function(d) { + return d.category; + }); + layer.select('.area').attr('d', function(d) { + return area(d.values); + }); + layer.enter().append('g').attr('class', function(d) { + return d.className; + }); + layer.append('path').attr('class', 'area').attr('d', function(d) { + return area(d.values); + }); + return Area.__super__.draw.call(this); + }; + + return Area; + +})(Epoch.Chart.Plot); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Chart.Bar = (function(superClass) { + var defaults, horizontal_defaults, horizontal_specific, optionListeners; + + extend(Bar, superClass); + + defaults = { + type: 'bar', + style: 'grouped', + orientation: 'vertical', + padding: { + bar: 0.08, + group: 0.1 + }, + outerPadding: { + bar: 0.08, + group: 0.1 + } + }; + + horizontal_specific = { + tickFormats: { + top: Epoch.Formats.si, + bottom: Epoch.Formats.si, + left: Epoch.Formats.regular, + right: Epoch.Formats.regular + } + }; + + horizontal_defaults = Epoch.Util.defaults(horizontal_specific, defaults); + + optionListeners = { + 'option:orientation': 'orientationChanged', + 'option:padding': 'paddingChanged', + 'option:outerPadding': 'paddingChanged', + 'option:padding:bar': 'paddingChanged', + 'option:padding:group': 'paddingChanged', + 'option:outerPadding:bar': 'paddingChanged', + 'option:outerPadding:group': 'paddingChanged' + }; + + function Bar(options) { + this.options = options != null ? options : {}; + if (this._isHorizontal()) { + this.options = Epoch.Util.defaults(this.options, horizontal_defaults); + } else { + this.options = Epoch.Util.defaults(this.options, defaults); + } + Bar.__super__.constructor.call(this, this.options); + this.onAll(optionListeners); + this.draw(); + } + + Bar.prototype._isVertical = function() { + return this.options.orientation === 'vertical'; + }; + + Bar.prototype._isHorizontal = function() { + return this.options.orientation === 'horizontal'; + }; + + Bar.prototype.x = function() { + var extent; + if (this._isVertical()) { + return d3.scale.ordinal().domain(Epoch.Util.domain(this.getVisibleLayers())).rangeRoundBands([0, this.innerWidth()], this.options.padding.group, this.options.outerPadding.group); + } else { + extent = this.extent(function(d) { + return d.y; + }); + extent[0] = Math.min(0, extent[0]); + return d3.scale.linear().domain(extent).range([0, this.width - this.margins.left - this.margins.right]); + } + }; + + Bar.prototype.x1 = function(x0) { + var layer; + return d3.scale.ordinal().domain((function() { + var j, len, ref, results; + ref = this.getVisibleLayers(); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + layer = ref[j]; + results.push(layer.category); + } + return results; + }).call(this)).rangeRoundBands([0, x0.rangeBand()], this.options.padding.bar, this.options.outerPadding.bar); + }; + + Bar.prototype.y = function() { + var extent; + if (this._isVertical()) { + extent = this.extent(function(d) { + return d.y; + }); + extent[0] = Math.min(0, extent[0]); + return d3.scale.linear().domain(extent).range([this.height - this.margins.top - this.margins.bottom, 0]); + } else { + return d3.scale.ordinal().domain(Epoch.Util.domain(this.getVisibleLayers())).rangeRoundBands([0, this.innerHeight()], this.options.padding.group, this.options.outerPadding.group); + } + }; + + Bar.prototype.y1 = function(y0) { + var layer; + return d3.scale.ordinal().domain((function() { + var j, len, ref, results; + ref = this.getVisibleLayers(); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + layer = ref[j]; + results.push(layer.category); + } + return results; + }).call(this)).rangeRoundBands([0, y0.rangeBand()], this.options.padding.bar, this.options.outerPadding.bar); + }; + + Bar.prototype._remapData = function() { + var className, entry, j, k, l, layer, len, len1, map, name, ref, ref1, results, v; + map = {}; + ref = this.getVisibleLayers(); + for (j = 0, len = ref.length; j < len; j++) { + layer = ref[j]; + className = 'bar ' + layer.className.replace(/\s*layer\s*/, ''); + ref1 = layer.values; + for (l = 0, len1 = ref1.length; l < len1; l++) { + entry = ref1[l]; + if (map[name = entry.x] == null) { + map[name] = []; + } + map[entry.x].push({ + label: layer.category, + y: entry.y, + className: className + }); + } + } + results = []; + for (k in map) { + if (!hasProp.call(map, k)) continue; + v = map[k]; + results.push({ + group: k, + values: v + }); + } + return results; + }; + + Bar.prototype.draw = function() { + if (this._isVertical()) { + this._drawVertical(); + } else { + this._drawHorizontal(); + } + return Bar.__super__.draw.call(this); + }; + + Bar.prototype._drawVertical = function() { + var data, height, layer, rects, ref, x0, x1, y; + ref = [this.x(), this.y()], x0 = ref[0], y = ref[1]; + x1 = this.x1(x0); + height = this.height - this.margins.top - this.margins.bottom; + data = this._remapData(); + layer = this.g.selectAll(".layer").data(data, function(d) { + return d.group; + }); + layer.transition().duration(750).attr("transform", function(d) { + return "translate(" + (x0(d.group)) + ", 0)"; + }); + layer.enter().append("g").attr('class', 'layer').attr("transform", function(d) { + return "translate(" + (x0(d.group)) + ", 0)"; + }); + rects = layer.selectAll('rect').data(function(group) { + return group.values; + }); + rects.attr('class', function(d) { + return d.className; + }); + rects.transition().duration(600).attr('x', function(d) { + return x1(d.label); + }).attr('y', function(d) { + return y(d.y); + }).attr('width', x1.rangeBand()).attr('height', function(d) { + return height - y(d.y); + }); + rects.enter().append('rect').attr('class', function(d) { + return d.className; + }).attr('x', function(d) { + return x1(d.label); + }).attr('y', function(d) { + return y(d.y); + }).attr('width', x1.rangeBand()).attr('height', function(d) { + return height - y(d.y); + }); + rects.exit().transition().duration(150).style('opacity', '0').remove(); + return layer.exit().transition().duration(750).style('opacity', '0').remove(); + }; + + Bar.prototype._drawHorizontal = function() { + var data, layer, rects, ref, width, x, y0, y1; + ref = [this.x(), this.y()], x = ref[0], y0 = ref[1]; + y1 = this.y1(y0); + width = this.width - this.margins.left - this.margins.right; + data = this._remapData(); + layer = this.g.selectAll(".layer").data(data, function(d) { + return d.group; + }); + layer.transition().duration(750).attr("transform", function(d) { + return "translate(0, " + (y0(d.group)) + ")"; + }); + layer.enter().append("g").attr('class', 'layer').attr("transform", function(d) { + return "translate(0, " + (y0(d.group)) + ")"; + }); + rects = layer.selectAll('rect').data(function(group) { + return group.values; + }); + rects.attr('class', function(d) { + return d.className; + }); + rects.transition().duration(600).attr('x', function(d) { + return 0; + }).attr('y', function(d) { + return y1(d.label); + }).attr('height', y1.rangeBand()).attr('width', function(d) { + return x(d.y); + }); + rects.enter().append('rect').attr('class', function(d) { + return d.className; + }).attr('x', function(d) { + return 0; + }).attr('y', function(d) { + return y1(d.label); + }).attr('height', y1.rangeBand()).attr('width', function(d) { + return x(d.y); + }); + rects.exit().transition().duration(150).style('opacity', '0').remove(); + return layer.exit().transition().duration(750).style('opacity', '0').remove(); + }; + + Bar.prototype._getTickValues = function(numTicks, dataKey) { + var i, step, tickValues, total; + if (dataKey == null) { + dataKey = 'x'; + } + if (this.data[0] == null) { + return []; + } + total = this.data[0].values.length; + step = Math.ceil(total / numTicks) | 0; + return tickValues = (function() { + var j, ref, ref1, results; + results = []; + for (i = j = 0, ref = total, ref1 = step; ref1 > 0 ? j < ref : j > ref; i = j += ref1) { + results.push(this.data[0].values[i].x); + } + return results; + }).call(this); + }; + + Bar.prototype.bottomAxis = function() { + var axis; + axis = d3.svg.axis().scale(this.x()).orient('bottom').ticks(this.options.ticks.bottom).tickFormat(this.options.tickFormats.bottom); + if (this._isVertical() && (this.options.ticks.bottom != null)) { + axis.tickValues(this._getTickValues(this.options.ticks.bottom)); + } + return axis; + }; + + Bar.prototype.topAxis = function() { + var axis; + axis = d3.svg.axis().scale(this.x()).orient('top').ticks(this.options.ticks.top).tickFormat(this.options.tickFormats.top); + if (this._isVertical() && (this.options.ticks.top != null)) { + axis.tickValues(this._getTickValues(this.options.ticks.top)); + } + return axis; + }; + + Bar.prototype.leftAxis = function() { + var axis; + axis = d3.svg.axis().scale(this.y()).orient('left').ticks(this.options.ticks.left).tickFormat(this.options.tickFormats.left); + if (this._isHorizontal() && (this.options.ticks.left != null)) { + axis.tickValues(this._getTickValues(this.options.ticks.left)); + } + return axis; + }; + + Bar.prototype.rightAxis = function() { + var axis; + axis = d3.svg.axis().scale(this.y()).orient('right').ticks(this.options.ticks.right).tickFormat(this.options.tickFormats.right); + if (this._isHorizontal() && (this.options.ticks.right != null)) { + axis.tickValues(this._getTickValues(this.options.ticks.right)); + } + return axis; + }; + + Bar.prototype.orientationChanged = function() { + var bottom, left, right, top; + top = this.options.tickFormats.top; + bottom = this.options.tickFormats.bottom; + left = this.options.tickFormats.left; + right = this.options.tickFormats.right; + this.options.tickFormats.left = top; + this.options.tickFormats.right = bottom; + this.options.tickFormats.top = left; + this.options.tickFormats.bottom = right; + return this.draw(); + }; + + Bar.prototype.paddingChanged = function() { + return this.draw(); + }; + + return Bar; + +})(Epoch.Chart.Plot); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Chart.Histogram = (function(superClass) { + var defaults, optionListeners; + + extend(Histogram, superClass); + + defaults = { + type: 'histogram', + domain: [0, 100], + bucketRange: [0, 100], + buckets: 10, + cutOutliers: false + }; + + optionListeners = { + 'option:bucketRange': 'bucketRangeChanged', + 'option:buckets': 'bucketsChanged', + 'option:cutOutliers': 'cutOutliersChanged' + }; + + function Histogram(options) { + this.options = options != null ? options : {}; + Histogram.__super__.constructor.call(this, this.options = Epoch.Util.defaults(this.options, defaults)); + this.onAll(optionListeners); + this.draw(); + } + + Histogram.prototype._prepareData = function(data) { + var bucketSize, buckets, i, index, j, k, l, layer, len, len1, point, prepared, preparedLayer, ref, v; + bucketSize = (this.options.bucketRange[1] - this.options.bucketRange[0]) / this.options.buckets; + prepared = []; + for (j = 0, len = data.length; j < len; j++) { + layer = data[j]; + buckets = (function() { + var l, ref, results; + results = []; + for (i = l = 0, ref = this.options.buckets; 0 <= ref ? l < ref : l > ref; i = 0 <= ref ? ++l : --l) { + results.push(0); + } + return results; + }).call(this); + ref = layer.values; + for (l = 0, len1 = ref.length; l < len1; l++) { + point = ref[l]; + index = parseInt((point.x - this.options.bucketRange[0]) / bucketSize); + if (this.options.cutOutliers && ((index < 0) || (index >= this.options.buckets))) { + continue; + } + if (index < 0) { + index = 0; + } else if (index >= this.options.buckets) { + index = this.options.buckets - 1; + } + buckets[index] += parseInt(point.y); + } + preparedLayer = { + values: buckets.map(function(d, i) { + return { + x: parseInt(i) * bucketSize, + y: d + }; + }) + }; + for (k in layer) { + if (!hasProp.call(layer, k)) continue; + v = layer[k]; + if (k !== 'values') { + preparedLayer[k] = v; + } + } + prepared.push(preparedLayer); + } + return prepared; + }; + + Histogram.prototype.resetData = function() { + this.setData(this.rawData); + return this.draw(); + }; + + Histogram.prototype.bucketRangeChanged = function() { + return this.resetData(); + }; + + Histogram.prototype.bucketsChanged = function() { + return this.resetData(); + }; + + Histogram.prototype.cutOutliersChanged = function() { + return this.resetData(); + }; + + return Histogram; + +})(Epoch.Chart.Bar); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Chart.Line = (function(superClass) { + extend(Line, superClass); + + function Line(options) { + var base; + this.options = options != null ? options : {}; + if ((base = this.options).type == null) { + base.type = 'line'; + } + Line.__super__.constructor.call(this, this.options); + this.draw(); + } + + Line.prototype.line = function(layer) { + var ref, x, y; + ref = [this.x(), this.y(layer.range)], x = ref[0], y = ref[1]; + return d3.svg.line().x(function(d) { + return x(d.x); + }).y(function(d) { + return y(d.y); + }); + }; + + Line.prototype.draw = function() { + var layer, layers, ref, x, y; + ref = [this.x(), this.y(), this.getVisibleLayers()], x = ref[0], y = ref[1], layers = ref[2]; + if (layers.length === 0) { + return this.g.selectAll('.layer').remove(); + } + layer = this.g.selectAll('.layer').data(layers, function(d) { + return d.category; + }); + layer.select('.line').transition().duration(500).attr('d', (function(_this) { + return function(l) { + return _this.line(l)(l.values); + }; + })(this)); + layer.enter().append('g').attr('class', function(l) { + return l.className; + }).append('path').attr('class', 'line').attr('d', (function(_this) { + return function(l) { + return _this.line(l)(l.values); + }; + })(this)); + layer.exit().transition().duration(750).style('opacity', '0').remove(); + return Line.__super__.draw.call(this); + }; + + return Line; + +})(Epoch.Chart.Plot); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Chart.Pie = (function(superClass) { + var defaults; + + extend(Pie, superClass); + + defaults = { + type: 'pie', + margin: 10, + inner: 0 + }; + + function Pie(options) { + this.options = options != null ? options : {}; + Pie.__super__.constructor.call(this, this.options = Epoch.Util.defaults(this.options, defaults)); + this.pie = d3.layout.pie().sort(null).value(function(d) { + return d.value; + }); + this.arc = d3.svg.arc().outerRadius((function(_this) { + return function() { + return (Math.max(_this.width, _this.height) / 2) - _this.options.margin; + }; + })(this)).innerRadius((function(_this) { + return function() { + return _this.options.inner; + }; + })(this)); + this.g = this.svg.append('g').attr("transform", "translate(" + (this.width / 2) + ", " + (this.height / 2) + ")"); + this.on('option:margin', 'marginChanged'); + this.on('option:inner', 'innerChanged'); + this.draw(); + } + + Pie.prototype.draw = function() { + var arcs, path, text; + this.g.selectAll('.arc').remove(); + arcs = this.g.selectAll(".arc").data(this.pie(this.getVisibleLayers()), function(d) { + return d.data.category; + }); + arcs.enter().append('g').attr('class', function(d) { + return "arc pie " + d.data.className; + }); + arcs.select('path').attr('d', this.arc); + arcs.select('text').attr("transform", (function(_this) { + return function(d) { + return "translate(" + (_this.arc.centroid(d)) + ")"; + }; + })(this)).text(function(d) { + return d.data.label || d.data.category; + }); + path = arcs.append("path").attr("d", this.arc).each(function(d) { + return this._current = d; + }); + text = arcs.append("text").attr("transform", (function(_this) { + return function(d) { + return "translate(" + (_this.arc.centroid(d)) + ")"; + }; + })(this)).attr("dy", ".35em").style("text-anchor", "middle").text(function(d) { + return d.data.label || d.data.category; + }); + return Pie.__super__.draw.call(this); + }; + + Pie.prototype.marginChanged = function() { + return this.draw(); + }; + + Pie.prototype.innerChanged = function() { + return this.draw(); + }; + + return Pie; + +})(Epoch.Chart.SVG); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Chart.Scatter = (function(superClass) { + var defaults; + + extend(Scatter, superClass); + + defaults = { + type: 'scatter', + radius: 3.5, + axes: ['top', 'bottom', 'left', 'right'] + }; + + function Scatter(options) { + this.options = options != null ? options : {}; + Scatter.__super__.constructor.call(this, this.options = Epoch.Util.defaults(this.options, defaults)); + this.on('option:radius', 'radiusChanged'); + this.draw(); + } + + Scatter.prototype.draw = function() { + var dots, layer, layers, radius, ref, x, y; + ref = [this.x(), this.y(), this.getVisibleLayers()], x = ref[0], y = ref[1], layers = ref[2]; + radius = this.options.radius; + if (layers.length === 0) { + return this.g.selectAll('.layer').remove(); + } + layer = this.g.selectAll('.layer').data(layers, function(d) { + return d.category; + }); + layer.enter().append('g').attr('class', function(d) { + return d.className; + }); + dots = layer.selectAll('.dot').data(function(l) { + return l.values; + }); + dots.transition().duration(500).attr("r", function(d) { + var ref1; + return (ref1 = d.r) != null ? ref1 : radius; + }).attr("cx", function(d) { + return x(d.x); + }).attr("cy", function(d) { + return y(d.y); + }); + dots.enter().append('circle').attr('class', 'dot').attr("r", function(d) { + var ref1; + return (ref1 = d.r) != null ? ref1 : radius; + }).attr("cx", function(d) { + return x(d.x); + }).attr("cy", function(d) { + return y(d.y); + }); + dots.exit().transition().duration(750).style('opacity', 0).remove(); + layer.exit().transition().duration(750).style('opacity', 0).remove(); + return Scatter.__super__.draw.call(this); + }; + + Scatter.prototype.radiusChanged = function() { + return this.draw(); + }; + + return Scatter; + +})(Epoch.Chart.Plot); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Time.Plot = (function(superClass) { + var defaultAxisMargins, defaults, optionListeners; + + extend(Plot, superClass); + + defaults = { + range: null, + fps: 24, + historySize: 120, + windowSize: 40, + queueSize: 10, + axes: ['bottom'], + ticks: { + time: 15, + left: 5, + right: 5 + }, + tickFormats: { + top: Epoch.Formats.seconds, + bottom: Epoch.Formats.seconds, + left: Epoch.Formats.si, + right: Epoch.Formats.si + } + }; + + defaultAxisMargins = { + top: 25, + right: 50, + bottom: 25, + left: 50 + }; + + optionListeners = { + 'option:margins': 'marginsChanged', + 'option:margins.top': 'marginsChanged', + 'option:margins.right': 'marginsChanged', + 'option:margins.bottom': 'marginsChanged', + 'option:margins.left': 'marginsChanged', + 'option:axes': 'axesChanged', + 'option:ticks': 'ticksChanged', + 'option:ticks.top': 'ticksChanged', + 'option:ticks.right': 'ticksChanged', + 'option:ticks.bottom': 'ticksChanged', + 'option:ticks.left': 'ticksChanged', + 'option:tickFormats': 'tickFormatsChanged', + 'option:tickFormats.top': 'tickFormatsChanged', + 'option:tickFormats.right': 'tickFormatsChanged', + 'option:tickFormats.bottom': 'tickFormatsChanged', + 'option:tickFormats.left': 'tickFormatsChanged' + }; + + function Plot(options) { + var givenMargins, l, len, pos, ref; + this.options = options; + givenMargins = Epoch.Util.copy(this.options.margins) || {}; + Plot.__super__.constructor.call(this, this.options = Epoch.Util.defaults(this.options, defaults)); + if (this.options.model) { + this.options.model.on('data:push', (function(_this) { + return function() { + return _this.pushFromModel(); + }; + })(this)); + } + this._queue = []; + this.margins = {}; + ref = ['top', 'right', 'bottom', 'left']; + for (l = 0, len = ref.length; l < len; l++) { + pos = ref[l]; + this.margins[pos] = (this.options.margins != null) && (this.options.margins[pos] != null) ? this.options.margins[pos] : this.hasAxis(pos) ? defaultAxisMargins[pos] : 6; + } + this.svg = this.el.insert('svg', ':first-child').attr('width', this.width).attr('height', this.height).style('z-index', '1000'); + if (this.el.style('position') !== 'absolute' && this.el.style('position') !== 'relative') { + this.el.style('position', 'relative'); + } + this.canvas.style({ + position: 'absolute', + 'z-index': '999' + }); + this._sizeCanvas(); + this.animation = { + interval: null, + active: false, + delta: (function(_this) { + return function() { + return -(_this.w() / _this.options.fps); + }; + })(this), + tickDelta: (function(_this) { + return function() { + return -((_this.w() / _this.pixelRatio) / _this.options.fps); + }; + })(this), + frame: 0, + duration: this.options.fps + }; + this._buildAxes(); + this.animationCallback = (function(_this) { + return function() { + return _this._animate(); + }; + })(this); + this.onAll(optionListeners); + } + + Plot.prototype._sizeCanvas = function() { + this.canvas.attr({ + width: this.innerWidth(), + height: this.innerHeight() + }); + return this.canvas.style({ + width: (this.innerWidth() / this.pixelRatio) + "px", + height: (this.innerHeight() / this.pixelRatio) + "px", + top: this.margins.top + "px", + left: this.margins.left + "px" + }); + }; + + Plot.prototype._buildAxes = function() { + this.svg.selectAll('.axis').remove(); + this._prepareTimeAxes(); + return this._prepareRangeAxes(); + }; + + Plot.prototype._annotateLayers = function(prepared) { + var classes, copy, data, i, layer, start; + data = []; + for (i in prepared) { + if (!hasProp.call(prepared, i)) continue; + layer = prepared[i]; + copy = Epoch.Util.copy(layer); + start = Math.max(0, layer.values.length - this.options.historySize); + copy.values = layer.values.slice(start); + classes = ['layer']; + classes.push("category" + ((i | 0) + 1)); + if (layer.label != null) { + classes.push(Epoch.Util.dasherize(layer.label)); + } + copy.className = classes.join(' '); + copy.visible = true; + data.push(copy); + } + return data; + }; + + Plot.prototype._offsetX = function() { + return 0; + }; + + Plot.prototype._prepareTimeAxes = function() { + var axis; + if (this.hasAxis('bottom')) { + axis = this.bottomAxis = this.svg.append('g').attr('class', "x axis bottom canvas").attr('transform', "translate(" + (this.margins.left - 1) + ", " + (this.innerHeight() / this.pixelRatio + this.margins.top) + ")"); + axis.append('path').attr('class', 'domain').attr('d', "M0,0H" + (this.innerWidth() / this.pixelRatio + 1)); + } + if (this.hasAxis('top')) { + axis = this.topAxis = this.svg.append('g').attr('class', "x axis top canvas").attr('transform', "translate(" + (this.margins.left - 1) + ", " + this.margins.top + ")"); + axis.append('path').attr('class', 'domain').attr('d', "M0,0H" + (this.innerWidth() / this.pixelRatio + 1)); + } + return this._resetInitialTimeTicks(); + }; + + Plot.prototype._resetInitialTimeTicks = function() { + var i, k, l, layer, len, ref, ref1, results, tickInterval; + tickInterval = this.options.ticks.time; + this._ticks = []; + this._tickTimer = tickInterval; + if (this.bottomAxis != null) { + this.bottomAxis.selectAll('.tick').remove(); + } + if (this.topAxis != null) { + this.topAxis.selectAll('.tick').remove(); + } + ref = this.data; + results = []; + for (l = 0, len = ref.length; l < len; l++) { + layer = ref[l]; + if (!Epoch.isNonEmptyArray(layer.values)) { + continue; + } + ref1 = [this.options.windowSize - 1, layer.values.length - 1], i = ref1[0], k = ref1[1]; + while (i >= 0 && k >= 0) { + this._pushTick(i, layer.values[k].time, false, true); + i -= tickInterval; + k -= tickInterval; + } + break; + } + return results; + }; + + Plot.prototype._prepareRangeAxes = function() { + if (this.hasAxis('left')) { + this.svg.append("g").attr("class", "y axis left").attr('transform', "translate(" + (this.margins.left - 1) + ", " + this.margins.top + ")").call(this.leftAxis()); + } + if (this.hasAxis('right')) { + return this.svg.append('g').attr('class', 'y axis right').attr('transform', "translate(" + (this.width - this.margins.right) + ", " + this.margins.top + ")").call(this.rightAxis()); + } + }; + + Plot.prototype.leftAxis = function() { + var axis, ticks; + ticks = this.options.ticks.left; + axis = d3.svg.axis().scale(this.ySvgLeft()).orient('left').tickFormat(this.options.tickFormats.left); + if (ticks === 2) { + return axis.tickValues(this.extent(function(d) { + return d.y; + })); + } else { + return axis.ticks(ticks); + } + }; + + Plot.prototype.rightAxis = function() { + var axis, extent, ticks; + extent = this.extent(function(d) { + return d.y; + }); + ticks = this.options.ticks.right; + axis = d3.svg.axis().scale(this.ySvgRight()).orient('right').tickFormat(this.options.tickFormats.right); + if (ticks === 2) { + return axis.tickValues(this.extent(function(d) { + return d.y; + })); + } else { + return axis.ticks(ticks); + } + }; + + Plot.prototype.hasAxis = function(name) { + return this.options.axes.indexOf(name) > -1; + }; + + Plot.prototype.innerWidth = function() { + return (this.width - (this.margins.left + this.margins.right)) * this.pixelRatio; + }; + + Plot.prototype.innerHeight = function() { + return (this.height - (this.margins.top + this.margins.bottom)) * this.pixelRatio; + }; + + Plot.prototype._prepareEntry = function(entry) { + return entry; + }; + + Plot.prototype._prepareLayers = function(layers) { + return layers; + }; + + Plot.prototype._startTransition = function() { + if (this.animation.active === true || this._queue.length === 0) { + return; + } + this.trigger('transition:start'); + this._shift(); + this.animation.active = true; + return this.animation.interval = setInterval(this.animationCallback, 1000 / this.options.fps); + }; + + Plot.prototype._stopTransition = function() { + var firstTick, l, lastTick, layer, len, ref, ref1; + if (!this.inTransition()) { + return; + } + ref = this.data; + for (l = 0, len = ref.length; l < len; l++) { + layer = ref[l]; + if (!(layer.values.length > this.options.windowSize + 1)) { + continue; + } + layer.values.shift(); + } + ref1 = [this._ticks[0], this._ticks[this._ticks.length - 1]], firstTick = ref1[0], lastTick = ref1[1]; + if ((lastTick != null) && lastTick.enter) { + lastTick.enter = false; + lastTick.opacity = 1; + } + if ((firstTick != null) && firstTick.exit) { + this._shiftTick(); + } + this.animation.frame = 0; + this.trigger('transition:end'); + if (this._queue.length > 0) { + return this._shift(); + } else { + this.animation.active = false; + return clearInterval(this.animation.interval); + } + }; + + Plot.prototype.inTransition = function() { + return this.animation.active; + }; + + Plot.prototype.push = function(layers) { + layers = this._prepareLayers(layers); + if (this._queue.length > this.options.queueSize) { + this._queue.splice(this.options.queueSize, this._queue.length - this.options.queueSize); + } + if (this._queue.length === this.options.queueSize) { + return false; + } + this._queue.push(layers.map((function(_this) { + return function(entry) { + return _this._prepareEntry(entry); + }; + })(this))); + this.trigger('push'); + if (!this.inTransition()) { + return this._startTransition(); + } + }; + + Plot.prototype.pushFromModel = function() { + return this.push(this.options.model.getNext(this.options.type, this.options.dataFormat)); + }; + + Plot.prototype._shift = function() { + var entry, i, layer, ref; + this.trigger('before:shift'); + entry = this._queue.shift(); + ref = this.data; + for (i in ref) { + if (!hasProp.call(ref, i)) continue; + layer = ref[i]; + layer.values.push(entry[i]); + } + this._updateTicks(entry[0].time); + this._transitionRangeAxes(); + return this.trigger('after:shift'); + }; + + Plot.prototype._transitionRangeAxes = function() { + if (this.hasAxis('left')) { + this.svg.selectAll('.y.axis.left').transition().duration(500).ease('linear').call(this.leftAxis()); + } + if (this.hasAxis('right')) { + return this.svg.selectAll('.y.axis.right').transition().duration(500).ease('linear').call(this.rightAxis()); + } + }; + + Plot.prototype._animate = function() { + if (!this.inTransition()) { + return; + } + if (++this.animation.frame === this.animation.duration) { + this._stopTransition(); + } + this.draw(this.animation.frame * this.animation.delta()); + return this._updateTimeAxes(); + }; + + Plot.prototype.y = function(givenDomain) { + return d3.scale.linear().domain(this._getScaleDomain(givenDomain)).range([this.innerHeight(), 0]); + }; + + Plot.prototype.ySvg = function(givenDomain) { + return d3.scale.linear().domain(this._getScaleDomain(givenDomain)).range([this.innerHeight() / this.pixelRatio, 0]); + }; + + Plot.prototype.ySvgLeft = function() { + if (this.options.range != null) { + return this.ySvg(this.options.range.left); + } else { + return this.ySvg(); + } + }; + + Plot.prototype.ySvgRight = function() { + if (this.options.range != null) { + return this.ySvg(this.options.range.right); + } else { + return this.ySvg(); + } + }; + + Plot.prototype.w = function() { + return this.innerWidth() / this.options.windowSize; + }; + + Plot.prototype._updateTicks = function(newTime) { + if (!(this.hasAxis('top') || this.hasAxis('bottom'))) { + return; + } + if (!((++this._tickTimer) % this.options.ticks.time)) { + this._pushTick(this.options.windowSize, newTime, true); + } + if (!(this._ticks.length > 0)) { + return; + } + if (!(this._ticks[0].x - (this.w() / this.pixelRatio) >= 0)) { + return this._ticks[0].exit = true; + } + }; + + Plot.prototype._pushTick = function(bucket, time, enter, reverse) { + var g, tick; + if (enter == null) { + enter = false; + } + if (reverse == null) { + reverse = false; + } + if (!(this.hasAxis('top') || this.hasAxis('bottom'))) { + return; + } + tick = { + time: time, + x: bucket * (this.w() / this.pixelRatio) + this._offsetX(), + opacity: enter ? 0 : 1, + enter: enter ? true : false, + exit: false + }; + if (this.hasAxis('bottom')) { + g = this.bottomAxis.append('g').attr('class', 'tick major').attr('transform', "translate(" + (tick.x + 1) + ",0)").style('opacity', tick.opacity); + g.append('line').attr('y2', 6); + g.append('text').attr('text-anchor', 'middle').attr('dy', 19).text(this.options.tickFormats.bottom(tick.time)); + tick.bottomEl = g; + } + if (this.hasAxis('top')) { + g = this.topAxis.append('g').attr('class', 'tick major').attr('transform', "translate(" + (tick.x + 1) + ",0)").style('opacity', tick.opacity); + g.append('line').attr('y2', -6); + g.append('text').attr('text-anchor', 'middle').attr('dy', -10).text(this.options.tickFormats.top(tick.time)); + tick.topEl = g; + } + if (reverse) { + this._ticks.unshift(tick); + } else { + this._ticks.push(tick); + } + return tick; + }; + + Plot.prototype._shiftTick = function() { + var tick; + if (!(this._ticks.length > 0)) { + return; + } + tick = this._ticks.shift(); + if (tick.topEl != null) { + tick.topEl.remove(); + } + if (tick.bottomEl != null) { + return tick.bottomEl.remove(); + } + }; + + Plot.prototype._updateTimeAxes = function() { + var dop, dx, l, len, ref, ref1, results, tick; + if (!(this.hasAxis('top') || this.hasAxis('bottom'))) { + return; + } + ref = [this.animation.tickDelta(), 1 / this.options.fps], dx = ref[0], dop = ref[1]; + ref1 = this._ticks; + results = []; + for (l = 0, len = ref1.length; l < len; l++) { + tick = ref1[l]; + tick.x += dx; + if (this.hasAxis('bottom')) { + tick.bottomEl.attr('transform', "translate(" + (tick.x + 1) + ",0)"); + } + if (this.hasAxis('top')) { + tick.topEl.attr('transform', "translate(" + (tick.x + 1) + ",0)"); + } + if (tick.enter) { + tick.opacity += dop; + } else if (tick.exit) { + tick.opacity -= dop; + } + if (tick.enter || tick.exit) { + if (this.hasAxis('bottom')) { + tick.bottomEl.style('opacity', tick.opacity); + } + if (this.hasAxis('top')) { + results.push(tick.topEl.style('opacity', tick.opacity)); + } else { + results.push(void 0); + } + } else { + results.push(void 0); + } + } + return results; + }; + + Plot.prototype.draw = function(delta) { + if (delta == null) { + delta = 0; + } + return Plot.__super__.draw.call(this); + }; + + Plot.prototype.dimensionsChanged = function() { + Plot.__super__.dimensionsChanged.call(this); + this.svg.attr('width', this.width).attr('height', this.height); + this._sizeCanvas(); + this._buildAxes(); + return this.draw(this.animation.frame * this.animation.delta()); + }; + + Plot.prototype.axesChanged = function() { + var l, len, pos, ref; + ref = ['top', 'right', 'bottom', 'left']; + for (l = 0, len = ref.length; l < len; l++) { + pos = ref[l]; + if ((this.options.margins != null) && (this.options.margins[pos] != null)) { + continue; + } + if (this.hasAxis(pos)) { + this.margins[pos] = defaultAxisMargins[pos]; + } else { + this.margins[pos] = 6; + } + } + this._sizeCanvas(); + this._buildAxes(); + return this.draw(this.animation.frame * this.animation.delta()); + }; + + Plot.prototype.ticksChanged = function() { + this._resetInitialTimeTicks(); + this._transitionRangeAxes(); + return this.draw(this.animation.frame * this.animation.delta()); + }; + + Plot.prototype.tickFormatsChanged = function() { + this._resetInitialTimeTicks(); + this._transitionRangeAxes(); + return this.draw(this.animation.frame * this.animation.delta()); + }; + + Plot.prototype.marginsChanged = function() { + var pos, ref, size; + if (this.options.margins == null) { + return; + } + ref = this.options.margins; + for (pos in ref) { + if (!hasProp.call(ref, pos)) continue; + size = ref[pos]; + if (size == null) { + this.margins[pos] = 6; + } else { + this.margins[pos] = size; + } + } + this._sizeCanvas(); + return this.draw(this.animation.frame * this.animation.delta()); + }; + + Plot.prototype.layerChanged = function() { + this._transitionRangeAxes(); + return Plot.__super__.layerChanged.call(this); + }; + + return Plot; + +})(Epoch.Chart.Canvas); + +Epoch.Time.Stack = (function(superClass) { + extend(Stack, superClass); + + function Stack() { + return Stack.__super__.constructor.apply(this, arguments); + } + + Stack.prototype._stackLayers = function() { + var i, l, layer, layers, ref, results, y0; + if (!((layers = this.getVisibleLayers()).length > 0)) { + return; + } + results = []; + for (i = l = 0, ref = layers[0].values.length; 0 <= ref ? l < ref : l > ref; i = 0 <= ref ? ++l : --l) { + y0 = 0; + results.push((function() { + var len, m, results1; + results1 = []; + for (m = 0, len = layers.length; m < len; m++) { + layer = layers[m]; + layer.values[i].y0 = y0; + results1.push(y0 += layer.values[i].y); + } + return results1; + })()); + } + return results; + }; + + Stack.prototype._prepareLayers = function(layers) { + var d, i, y0; + y0 = 0; + for (i in layers) { + if (!hasProp.call(layers, i)) continue; + d = layers[i]; + if (!this.data[i].visible) { + continue; + } + d.y0 = y0; + y0 += d.y; + } + return layers; + }; + + Stack.prototype.setData = function(data) { + Stack.__super__.setData.call(this, data); + return this._stackLayers(); + }; + + Stack.prototype.extent = function() { + var i, j, l, layers, m, max, ref, ref1, ref2, sum; + ref = [0, this.getVisibleLayers()], max = ref[0], layers = ref[1]; + if (!layers.length) { + return [0, 0]; + } + for (i = l = 0, ref1 = layers[0].values.length; 0 <= ref1 ? l < ref1 : l > ref1; i = 0 <= ref1 ? ++l : --l) { + sum = 0; + for (j = m = 0, ref2 = layers.length; 0 <= ref2 ? m < ref2 : m > ref2; j = 0 <= ref2 ? ++m : --m) { + sum += layers[j].values[i].y; + } + if (sum > max) { + max = sum; + } + } + return [0, max]; + }; + + Stack.prototype.layerChanged = function() { + var l, layers, len, ref; + this._stackLayers(); + ref = this._queue; + for (l = 0, len = ref.length; l < len; l++) { + layers = ref[l]; + this._prepareLayers(layers); + } + return Stack.__super__.layerChanged.call(this); + }; + + return Stack; + +})(Epoch.Time.Plot); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Time.Area = (function(superClass) { + extend(Area, superClass); + + function Area(options) { + var base; + this.options = options != null ? options : {}; + if ((base = this.options).type == null) { + base.type = 'time.area'; + } + Area.__super__.constructor.call(this, this.options); + this.draw(); + } + + Area.prototype.setStyles = function(layer) { + var styles; + if ((layer != null) && (layer.className != null)) { + styles = this.getStyles("g." + (layer.className.replace(/\s/g, '.')) + " path.area"); + } else { + styles = this.getStyles("g path.area"); + } + this.ctx.fillStyle = styles.fill; + if (styles.stroke != null) { + this.ctx.strokeStyle = styles.stroke; + } + if (styles['stroke-width'] != null) { + return this.ctx.lineWidth = styles['stroke-width'].replace('px', ''); + } + }; + + Area.prototype._drawAreas = function(delta) { + var args, borderX, entry, firstX, i, j, k, l, layer, layers, ref, ref1, ref2, results, trans, w, y; + if (delta == null) { + delta = 0; + } + ref = [this.y(), this.w(), this.getVisibleLayers()], y = ref[0], w = ref[1], layers = ref[2]; + results = []; + for (i = l = ref1 = layers.length - 1; ref1 <= 0 ? l <= 0 : l >= 0; i = ref1 <= 0 ? ++l : --l) { + if (!(layer = layers[i])) { + continue; + } + this.setStyles(layer); + this.ctx.beginPath(); + ref2 = [this.options.windowSize, layer.values.length, this.inTransition()], j = ref2[0], k = ref2[1], trans = ref2[2]; + firstX = null; + while ((--j >= -2) && (--k >= 0)) { + entry = layer.values[k]; + args = [(j + 1) * w + delta, y(entry.y + entry.y0)]; + if (trans) { + args[0] += w; + } + if (i === this.options.windowSize - 1) { + this.ctx.moveTo.apply(this.ctx, args); + } else { + this.ctx.lineTo.apply(this.ctx, args); + } + } + if (trans) { + borderX = (j + 3) * w + delta; + } else { + borderX = (j + 2) * w + delta; + } + this.ctx.lineTo(borderX, this.innerHeight()); + this.ctx.lineTo(this.width * this.pixelRatio + w + delta, this.innerHeight()); + this.ctx.closePath(); + results.push(this.ctx.fill()); + } + return results; + }; + + Area.prototype._drawStrokes = function(delta) { + var args, entry, firstX, i, k, l, layer, layers, ref, ref1, ref2, results, trans, w, y; + if (delta == null) { + delta = 0; + } + ref = [this.y(), this.w(), this.getVisibleLayers()], y = ref[0], w = ref[1], layers = ref[2]; + results = []; + for (i = l = ref1 = layers.length - 1; ref1 <= 0 ? l <= 0 : l >= 0; i = ref1 <= 0 ? ++l : --l) { + if (!(layer = layers[i])) { + continue; + } + this.setStyles(layer); + this.ctx.beginPath(); + ref2 = [this.options.windowSize, layer.values.length, this.inTransition()], i = ref2[0], k = ref2[1], trans = ref2[2]; + firstX = null; + while ((--i >= -2) && (--k >= 0)) { + entry = layer.values[k]; + args = [(i + 1) * w + delta, y(entry.y + entry.y0)]; + if (trans) { + args[0] += w; + } + if (i === this.options.windowSize - 1) { + this.ctx.moveTo.apply(this.ctx, args); + } else { + this.ctx.lineTo.apply(this.ctx, args); + } + } + results.push(this.ctx.stroke()); + } + return results; + }; + + Area.prototype.draw = function(delta) { + if (delta == null) { + delta = 0; + } + this.clear(); + this._drawAreas(delta); + this._drawStrokes(delta); + return Area.__super__.draw.call(this); + }; + + return Area; + +})(Epoch.Time.Stack); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Time.Bar = (function(superClass) { + extend(Bar, superClass); + + function Bar(options) { + var base; + this.options = options != null ? options : {}; + if ((base = this.options).type == null) { + base.type = 'time.bar'; + } + Bar.__super__.constructor.call(this, this.options); + this.draw(); + } + + Bar.prototype._offsetX = function() { + return 0.5 * this.w() / this.pixelRatio; + }; + + Bar.prototype.setStyles = function(className) { + var styles; + styles = this.getStyles("rect.bar." + (className.replace(/\s/g, '.'))); + this.ctx.fillStyle = styles.fill; + if ((styles.stroke == null) || styles.stroke === 'none') { + this.ctx.strokeStyle = 'transparent'; + } else { + this.ctx.strokeStyle = styles.stroke; + } + if (styles['stroke-width'] != null) { + return this.ctx.lineWidth = styles['stroke-width'].replace('px', ''); + } + }; + + Bar.prototype.draw = function(delta) { + var args, entry, ex, ey, ey0, i, iBoundry, j, k, layer, len, ref, ref1, ref2, ref3, trans, w, y; + if (delta == null) { + delta = 0; + } + this.clear(); + ref = [this.y(), this.w()], y = ref[0], w = ref[1]; + ref1 = this.getVisibleLayers(); + for (j = 0, len = ref1.length; j < len; j++) { + layer = ref1[j]; + if (!Epoch.isNonEmptyArray(layer.values)) { + continue; + } + this.setStyles(layer.className); + ref2 = [this.options.windowSize, layer.values.length, this.inTransition()], i = ref2[0], k = ref2[1], trans = ref2[2]; + iBoundry = trans ? -1 : 0; + while ((--i >= iBoundry) && (--k >= 0)) { + entry = layer.values[k]; + ref3 = [i * w + delta, entry.y, entry.y0], ex = ref3[0], ey = ref3[1], ey0 = ref3[2]; + if (trans) { + ex += w; + } + args = [ex + 1, y(ey + ey0), w - 2, this.innerHeight() - y(ey) + 0.5 * this.pixelRatio]; + this.ctx.fillRect.apply(this.ctx, args); + this.ctx.strokeRect.apply(this.ctx, args); + } + } + return Bar.__super__.draw.call(this); + }; + + return Bar; + +})(Epoch.Time.Stack); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Time.Gauge = (function(superClass) { + var defaults, optionListeners; + + extend(Gauge, superClass); + + defaults = { + type: 'time.gauge', + domain: [0, 1], + ticks: 10, + tickSize: 5, + tickOffset: 5, + fps: 34, + format: Epoch.Formats.percent + }; + + optionListeners = { + 'option:domain': 'domainChanged', + 'option:ticks': 'ticksChanged', + 'option:tickSize': 'tickSizeChanged', + 'option:tickOffset': 'tickOffsetChanged', + 'option:format': 'formatChanged' + }; + + function Gauge(options) { + this.options = options != null ? options : {}; + Gauge.__super__.constructor.call(this, this.options = Epoch.Util.defaults(this.options, defaults)); + this.value = this.options.value || 0; + if (this.options.model) { + this.options.model.on('data:push', (function(_this) { + return function() { + return _this.pushFromModel(); + }; + })(this)); + } + if (this.el.style('position') !== 'absolute' && this.el.style('position') !== 'relative') { + this.el.style('position', 'relative'); + } + this.svg = this.el.insert('svg', ':first-child').attr('width', this.width).attr('height', this.height).attr('class', 'gauge-labels'); + this.svg.style({ + 'position': 'absolute', + 'z-index': '1' + }); + this.svg.append('g').attr('transform', "translate(" + (this.textX()) + ", " + (this.textY()) + ")").append('text').attr('class', 'value').text(this.options.format(this.value)); + this.animation = { + interval: null, + active: false, + delta: 0, + target: 0 + }; + this._animate = (function(_this) { + return function() { + if (Math.abs(_this.animation.target - _this.value) < Math.abs(_this.animation.delta)) { + _this.value = _this.animation.target; + clearInterval(_this.animation.interval); + _this.animation.active = false; + } else { + _this.value += _this.animation.delta; + } + _this.svg.select('text.value').text(_this.options.format(_this.value)); + return _this.draw(); + }; + })(this); + this.onAll(optionListeners); + this.draw(); + } + + Gauge.prototype.update = function(value) { + this.animation.target = value; + this.animation.delta = (value - this.value) / this.options.fps; + if (!this.animation.active) { + this.animation.interval = setInterval(this._animate, 1000 / this.options.fps); + return this.animation.active = true; + } + }; + + Gauge.prototype.push = function(value) { + return this.update(value); + }; + + Gauge.prototype.pushFromModel = function() { + var next; + next = this.options.model.getNext(this.options.type, this.options.dataFormat); + return this.update(next); + }; + + Gauge.prototype.radius = function() { + return this.getHeight() / 1.58; + }; + + Gauge.prototype.centerX = function() { + return this.getWidth() / 2; + }; + + Gauge.prototype.centerY = function() { + return 0.68 * this.getHeight(); + }; + + Gauge.prototype.textX = function() { + return this.width / 2; + }; + + Gauge.prototype.textY = function() { + return 0.48 * this.height; + }; + + Gauge.prototype.getAngle = function(value) { + var a, b, ref; + ref = this.options.domain, a = ref[0], b = ref[1]; + return ((value - a) / (b - a)) * (Math.PI + 2 * Math.PI / 8) - Math.PI / 2 - Math.PI / 8; + }; + + Gauge.prototype.setStyles = function(selector) { + var styles; + styles = this.getStyles(selector); + this.ctx.fillStyle = styles.fill; + this.ctx.strokeStyle = styles.stroke; + if (styles['stroke-width'] != null) { + return this.ctx.lineWidth = styles['stroke-width'].replace('px', ''); + } + }; + + Gauge.prototype.draw = function() { + var a, c, cx, cy, i, j, r, ref, ref1, ref2, ref3, s, t, tickOffset, tickSize, x1, x2, y1, y2; + ref = [this.centerX(), this.centerY(), this.radius()], cx = ref[0], cy = ref[1], r = ref[2]; + ref1 = [this.options.tickOffset, this.options.tickSize], tickOffset = ref1[0], tickSize = ref1[1]; + this.clear(); + t = d3.scale.linear().domain([0, this.options.ticks]).range([-(9 / 8) * Math.PI, Math.PI / 8]); + this.setStyles('.epoch .gauge .tick'); + this.ctx.beginPath(); + for (i = j = 0, ref2 = this.options.ticks; 0 <= ref2 ? j <= ref2 : j >= ref2; i = 0 <= ref2 ? ++j : --j) { + a = t(i); + ref3 = [Math.cos(a), Math.sin(a)], c = ref3[0], s = ref3[1]; + x1 = c * (r - tickOffset) + cx; + y1 = s * (r - tickOffset) + cy; + x2 = c * (r - tickOffset - tickSize) + cx; + y2 = s * (r - tickOffset - tickSize) + cy; + this.ctx.moveTo(x1, y1); + this.ctx.lineTo(x2, y2); + } + this.ctx.stroke(); + this.setStyles('.epoch .gauge .arc.outer'); + this.ctx.beginPath(); + this.ctx.arc(cx, cy, r, -(9 / 8) * Math.PI, (1 / 8) * Math.PI, false); + this.ctx.stroke(); + this.setStyles('.epoch .gauge .arc.inner'); + this.ctx.beginPath(); + this.ctx.arc(cx, cy, r - 10, -(9 / 8) * Math.PI, (1 / 8) * Math.PI, false); + this.ctx.stroke(); + this.drawNeedle(); + return Gauge.__super__.draw.call(this); + }; + + Gauge.prototype.drawNeedle = function() { + var cx, cy, r, ratio, ref; + ref = [this.centerX(), this.centerY(), this.radius()], cx = ref[0], cy = ref[1], r = ref[2]; + ratio = this.value / this.options.domain[1]; + this.setStyles('.epoch .gauge .needle'); + this.ctx.beginPath(); + this.ctx.save(); + this.ctx.translate(cx, cy); + this.ctx.rotate(this.getAngle(this.value)); + this.ctx.moveTo(4 * this.pixelRatio, 0); + this.ctx.lineTo(-4 * this.pixelRatio, 0); + this.ctx.lineTo(-1 * this.pixelRatio, 19 - r); + this.ctx.lineTo(1, 19 - r); + this.ctx.fill(); + this.setStyles('.epoch .gauge .needle-base'); + this.ctx.beginPath(); + this.ctx.arc(0, 0, this.getWidth() / 25, 0, 2 * Math.PI); + this.ctx.fill(); + return this.ctx.restore(); + }; + + Gauge.prototype.domainChanged = function() { + return this.draw(); + }; + + Gauge.prototype.ticksChanged = function() { + return this.draw(); + }; + + Gauge.prototype.tickSizeChanged = function() { + return this.draw(); + }; + + Gauge.prototype.tickOffsetChanged = function() { + return this.draw(); + }; + + Gauge.prototype.formatChanged = function() { + return this.svg.select('text.value').text(this.options.format(this.value)); + }; + + return Gauge; + +})(Epoch.Chart.Canvas); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Time.Heatmap = (function(superClass) { + var colorFunctions, defaults, optionListeners; + + extend(Heatmap, superClass); + + defaults = { + type: 'time.heatmap', + buckets: 10, + bucketRange: [0, 100], + opacity: 'linear', + bucketPadding: 2, + paintZeroValues: false, + cutOutliers: false + }; + + colorFunctions = { + root: function(value, max) { + return Math.pow(value / max, 0.5); + }, + linear: function(value, max) { + return value / max; + }, + quadratic: function(value, max) { + return Math.pow(value / max, 2); + }, + cubic: function(value, max) { + return Math.pow(value / max, 3); + }, + quartic: function(value, max) { + return Math.pow(value / max, 4); + }, + quintic: function(value, max) { + return Math.pow(value / max, 5); + } + }; + + optionListeners = { + 'option:buckets': 'bucketsChanged', + 'option:bucketRange': 'bucketRangeChanged', + 'option:opacity': 'opacityChanged', + 'option:bucketPadding': 'bucketPaddingChanged', + 'option:paintZeroValues': 'paintZeroValuesChanged', + 'option:cutOutliers': 'cutOutliersChanged' + }; + + function Heatmap(options) { + this.options = options != null ? options : {}; + Heatmap.__super__.constructor.call(this, this.options = Epoch.Util.defaults(this.options, defaults)); + this._setOpacityFunction(); + this._setupPaintCanvas(); + this.onAll(optionListeners); + this.draw(); + } + + Heatmap.prototype._setOpacityFunction = function() { + if (Epoch.isString(this.options.opacity)) { + this._opacityFn = colorFunctions[this.options.opacity]; + if (this._opacityFn == null) { + return Epoch.exception("Unknown coloring function provided '" + this.options.opacity + "'"); + } + } else if (Epoch.isFunction(this.options.opacity)) { + return this._opacityFn = this.options.opacity; + } else { + return Epoch.exception("Unknown type for provided coloring function."); + } + }; + + Heatmap.prototype.setData = function(data) { + var k, layer, len, ref, results; + Heatmap.__super__.setData.call(this, data); + ref = this.data; + results = []; + for (k = 0, len = ref.length; k < len; k++) { + layer = ref[k]; + results.push(layer.values = layer.values.map((function(_this) { + return function(entry) { + return _this._prepareEntry(entry); + }; + })(this))); + } + return results; + }; + + Heatmap.prototype._getBuckets = function(entry) { + var bucketSize, count, i, index, k, prepared, ref, ref1, value; + prepared = { + time: entry.time, + max: 0, + buckets: (function() { + var k, ref, results; + results = []; + for (i = k = 0, ref = this.options.buckets; 0 <= ref ? k < ref : k > ref; i = 0 <= ref ? ++k : --k) { + results.push(0); + } + return results; + }).call(this) + }; + bucketSize = (this.options.bucketRange[1] - this.options.bucketRange[0]) / this.options.buckets; + ref = entry.histogram; + for (value in ref) { + if (!hasProp.call(ref, value)) continue; + count = ref[value]; + index = parseInt((value - this.options.bucketRange[0]) / bucketSize); + if (this.options.cutOutliers && ((index < 0) || (index >= this.options.buckets))) { + continue; + } + if (index < 0) { + index = 0; + } else if (index >= this.options.buckets) { + index = this.options.buckets - 1; + } + prepared.buckets[index] += parseInt(count); + } + for (i = k = 0, ref1 = prepared.buckets.length; 0 <= ref1 ? k < ref1 : k > ref1; i = 0 <= ref1 ? ++k : --k) { + prepared.max = Math.max(prepared.max, prepared.buckets[i]); + } + return prepared; + }; + + Heatmap.prototype.y = function() { + return d3.scale.linear().domain(this.options.bucketRange).range([this.innerHeight(), 0]); + }; + + Heatmap.prototype.ySvg = function() { + return d3.scale.linear().domain(this.options.bucketRange).range([this.innerHeight() / this.pixelRatio, 0]); + }; + + Heatmap.prototype.h = function() { + return this.innerHeight() / this.options.buckets; + }; + + Heatmap.prototype._offsetX = function() { + return 0.5 * this.w() / this.pixelRatio; + }; + + Heatmap.prototype._setupPaintCanvas = function() { + this.paintWidth = (this.options.windowSize + 1) * this.w(); + this.paintHeight = this.height * this.pixelRatio; + this.paint = document.createElement('CANVAS'); + this.paint.width = this.paintWidth; + this.paint.height = this.paintHeight; + this.p = Epoch.Util.getContext(this.paint); + this.redraw(); + this.on('after:shift', '_paintEntry'); + this.on('transition:end', '_shiftPaintCanvas'); + return this.on('transition:end', (function(_this) { + return function() { + return _this.draw(_this.animation.frame * _this.animation.delta()); + }; + })(this)); + }; + + Heatmap.prototype.redraw = function() { + var drawColumn, entryIndex; + if (!(Epoch.isNonEmptyArray(this.data) && Epoch.isNonEmptyArray(this.data[0].values))) { + return; + } + entryIndex = this.data[0].values.length; + drawColumn = this.options.windowSize; + if (this.inTransition()) { + drawColumn++; + } + while ((--entryIndex >= 0) && (--drawColumn >= 0)) { + this._paintEntry(entryIndex, drawColumn); + } + return this.draw(this.animation.frame * this.animation.delta()); + }; + + Heatmap.prototype._computeColor = function(value, max, color) { + return Epoch.Util.toRGBA(color, this._opacityFn(value, max)); + }; + + Heatmap.prototype._paintEntry = function(entryIndex, drawColumn) { + var bucket, bucketTotals, color, count, entries, entry, h, i, j, k, layer, len, len1, m, max, maxTotal, ref, ref1, ref2, results, styles, sum, w, xPos; + if (entryIndex == null) { + entryIndex = null; + } + if (drawColumn == null) { + drawColumn = null; + } + ref = [this.w(), this.h()], w = ref[0], h = ref[1]; + if (entryIndex == null) { + entryIndex = this.data[0].values.length - 1; + } + if (drawColumn == null) { + drawColumn = this.options.windowSize; + } + entries = []; + bucketTotals = (function() { + var k, ref1, results; + results = []; + for (i = k = 0, ref1 = this.options.buckets; 0 <= ref1 ? k < ref1 : k > ref1; i = 0 <= ref1 ? ++k : --k) { + results.push(0); + } + return results; + }).call(this); + maxTotal = 0; + ref1 = this.getVisibleLayers(); + for (k = 0, len = ref1.length; k < len; k++) { + layer = ref1[k]; + entry = this._getBuckets(layer.values[entryIndex]); + ref2 = entry.buckets; + for (bucket in ref2) { + if (!hasProp.call(ref2, bucket)) continue; + count = ref2[bucket]; + bucketTotals[bucket] += count; + } + maxTotal += entry.max; + styles = this.getStyles("." + (layer.className.split(' ').join('.')) + " rect.bucket"); + entry.color = styles.fill; + entries.push(entry); + } + xPos = drawColumn * w; + this.p.clearRect(xPos, 0, w, this.paintHeight); + j = this.options.buckets; + results = []; + for (bucket in bucketTotals) { + if (!hasProp.call(bucketTotals, bucket)) continue; + sum = bucketTotals[bucket]; + color = this._avgLab(entries, bucket); + max = 0; + for (m = 0, len1 = entries.length; m < len1; m++) { + entry = entries[m]; + max += (entry.buckets[bucket] / sum) * maxTotal; + } + if (sum > 0 || this.options.paintZeroValues) { + this.p.fillStyle = this._computeColor(sum, max, color); + this.p.fillRect(xPos, (j - 1) * h, w - this.options.bucketPadding, h - this.options.bucketPadding); + } + results.push(j--); + } + return results; + }; + + Heatmap.prototype._shiftPaintCanvas = function() { + var data; + data = this.p.getImageData(this.w(), 0, this.paintWidth - this.w(), this.paintHeight); + return this.p.putImageData(data, 0, 0); + }; + + Heatmap.prototype._avgLab = function(entries, bucket) { + var a, b, color, entry, i, k, l, len, ratio, ref, total, value; + ref = [0, 0, 0, 0], l = ref[0], a = ref[1], b = ref[2], total = ref[3]; + for (k = 0, len = entries.length; k < len; k++) { + entry = entries[k]; + if (entry.buckets[bucket] == null) { + continue; + } + total += entry.buckets[bucket]; + } + for (i in entries) { + if (!hasProp.call(entries, i)) continue; + entry = entries[i]; + if (entry.buckets[bucket] != null) { + value = entry.buckets[bucket] | 0; + } else { + value = 0; + } + ratio = value / total; + color = d3.lab(entry.color); + l += ratio * color.l; + a += ratio * color.a; + b += ratio * color.b; + } + return d3.lab(l, a, b).toString(); + }; + + Heatmap.prototype.draw = function(delta) { + if (delta == null) { + delta = 0; + } + this.clear(); + this.ctx.drawImage(this.paint, delta, 0); + return Heatmap.__super__.draw.call(this); + }; + + Heatmap.prototype.bucketsChanged = function() { + return this.redraw(); + }; + + Heatmap.prototype.bucketRangeChanged = function() { + this._transitionRangeAxes(); + return this.redraw(); + }; + + Heatmap.prototype.opacityChanged = function() { + this._setOpacityFunction(); + return this.redraw(); + }; + + Heatmap.prototype.bucketPaddingChanged = function() { + return this.redraw(); + }; + + Heatmap.prototype.paintZeroValuesChanged = function() { + return this.redraw(); + }; + + Heatmap.prototype.cutOutliersChanged = function() { + return this.redraw(); + }; + + Heatmap.prototype.layerChanged = function() { + return this.redraw(); + }; + + return Heatmap; + +})(Epoch.Time.Plot); + +var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +Epoch.Time.Line = (function(superClass) { + extend(Line, superClass); + + function Line(options) { + var base; + this.options = options != null ? options : {}; + if ((base = this.options).type == null) { + base.type = 'time.line'; + } + Line.__super__.constructor.call(this, this.options); + this.draw(); + } + + Line.prototype.setStyles = function(className) { + var styles; + styles = this.getStyles("g." + (className.replace(/\s/g, '.')) + " path.line"); + this.ctx.fillStyle = styles.fill; + this.ctx.strokeStyle = styles.stroke; + return this.ctx.lineWidth = this.pixelRatio * styles['stroke-width'].replace('px', ''); + }; + + Line.prototype.draw = function(delta) { + var args, entry, i, j, k, layer, len, ref, ref1, trans, w, y; + if (delta == null) { + delta = 0; + } + this.clear(); + w = this.w(); + ref = this.getVisibleLayers(); + for (j = 0, len = ref.length; j < len; j++) { + layer = ref[j]; + if (!Epoch.isNonEmptyArray(layer.values)) { + continue; + } + this.setStyles(layer.className); + this.ctx.beginPath(); + y = this.y(layer.range); + ref1 = [this.options.windowSize, layer.values.length, this.inTransition()], i = ref1[0], k = ref1[1], trans = ref1[2]; + while ((--i >= -2) && (--k >= 0)) { + entry = layer.values[k]; + args = [(i + 1) * w + delta, y(entry.y)]; + if (trans) { + args[0] += w; + } + if (i === this.options.windowSize - 1) { + this.ctx.moveTo.apply(this.ctx, args); + } else { + this.ctx.lineTo.apply(this.ctx, args); + } + } + this.ctx.stroke(); + } + return Line.__super__.draw.call(this); + }; + + return Line; + +})(Epoch.Time.Plot); + +Epoch._typeMap = { + 'area': Epoch.Chart.Area, + 'bar': Epoch.Chart.Bar, + 'line': Epoch.Chart.Line, + 'pie': Epoch.Chart.Pie, + 'scatter': Epoch.Chart.Scatter, + 'histogram': Epoch.Chart.Histogram, + 'time.area': Epoch.Time.Area, + 'time.bar': Epoch.Time.Bar, + 'time.line': Epoch.Time.Line, + 'time.gauge': Epoch.Time.Gauge, + 'time.heatmap': Epoch.Time.Heatmap +}; + +var jQueryModule; + +jQueryModule = function($) { + var DATA_NAME; + DATA_NAME = 'epoch-chart'; + return $.fn.epoch = function(options) { + var chart, klass; + options.el = this.get(0); + if ((chart = this.data(DATA_NAME)) == null) { + klass = Epoch._typeMap[options.type]; + if (klass == null) { + Epoch.exception("Unknown chart type '" + options.type + "'"); + } + this.data(DATA_NAME, (chart = new klass(options))); + } + return chart; + }; +}; + +if (window.jQuery != null) { + jQueryModule(jQuery); +} + +var MooToolsModule; + +MooToolsModule = function() { + var DATA_NAME; + DATA_NAME = 'epoch-chart'; + return Element.implement('epoch', function(options) { + var chart, klass, self; + self = $$(this); + if ((chart = self.retrieve(DATA_NAME)[0]) == null) { + options.el = this; + klass = Epoch._typeMap[options.type]; + if (klass == null) { + Epoch.exception("Unknown chart type '" + options.type + "'"); + } + self.store(DATA_NAME, (chart = new klass(options))); + } + return chart; + }); +}; + +if (window.MooTools != null) { + MooToolsModule(); +} + +var zeptoModule; + +zeptoModule = function($) { + var DATA_NAME, chartId, chartMap, next_cid; + DATA_NAME = 'epoch-chart'; + chartMap = {}; + chartId = 0; + next_cid = function() { + return DATA_NAME + "-" + (++chartId); + }; + return $.extend($.fn, { + epoch: function(options) { + var chart, cid, klass; + if ((cid = this.data(DATA_NAME)) != null) { + return chartMap[cid]; + } + options.el = this.get(0); + klass = Epoch._typeMap[options.type]; + if (klass == null) { + Epoch.exception("Unknown chart type '" + options.type + "'"); + } + this.data(DATA_NAME, (cid = next_cid())); + chart = new klass(options); + chartMap[cid] = chart; + return chart; + } + }); +}; + +if (window.Zepto != null) { + zeptoModule(Zepto); +} diff --git a/debian/missing-sources/epoch/.travis.yml b/debian/missing-sources/epoch/.travis.yml new file mode 100644 index 0000000..c3bbff7 --- /dev/null +++ b/debian/missing-sources/epoch/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "4.1.1" +sudo: false diff --git a/debian/missing-sources/epoch/CHANGELOG.md b/debian/missing-sources/epoch/CHANGELOG.md new file mode 100644 index 0000000..0492850 --- /dev/null +++ b/debian/missing-sources/epoch/CHANGELOG.md @@ -0,0 +1,102 @@ +# Epoch Changelog + +## 0.8.4 - October 30th, 2015 +### Bug Fixes +* Fixed bower css path (@ftaiolivista) + +## 0.8.3 - October 17th, 2015 +### Enhancements / Features +* Added `redraw` method for clearing styles on canvas based charts (#196, @woozyking) + +## 0.8.2 - October 13th, 2015 +### Enhancements / Features +* Charts now auto draw on construction (#195) + +## 0.8.1 - October 13th, 2015 +### Enhancements / Features +* Added packagist/composer package manager support (#202) + +### Bug Fixes +* Real-time charts no-longer error when pushing first data point after initialized + with empty data layers. (#203) + +## 0.8.0 - October 10th, 2015 +### Enhancements / Features +* Multi-axis support for basic and real-time line plots +* Added new gulp build-system (for development) + +## 0.7.1 - October 4th, 2015 +* Moved minified source to `dist/js` and `dist/css` respectively +* Added non-minified source to aforementioned directories + +## 0.7.0 - October 4th, 2015 + +### Enhancements / Features +* New basic chart: Histogram +* New Feature: Data formatters +* Chart layers can now be hidden/shown + +### Bug Fixes +* Ticks now working for ordinal scaled bar charts +* Fixed CSS builds by updating NPM sass-node package +* Removed versions from minified release files (@RyanNielson) +* Time based graphs can now have fixed ranges (@willwhitney) +* NPM Package: epoch-charting (@sompylasar) +* Right axes now using correct formatters (@Dav1dde) +* Add 'main' attribute enabling webpack support. (@WRidder) +* Fixed Bower D3 Dependencies (@loopj) +* Fixed CSS errors by using `transparent` instead of `none` (@mwsmith2) +* Fixed bower "version" property (@kkirsche) + +## 0.6.0 - July 21st, 2014 + +### Enhancements / Features + +* Source code restructure for easier programming +* Replaced Compass with node-sass +* Removed put.js from the repository +* Removed dependency on jQuery +* Added CSS controlled themes + * New "Dark" theme for dark backgrounds +* Registered with bower +* Added option accessor / mutator to all charts (making them adaptive) +* Added bubble charts (special case of scatter plots) +* Added MooTools and Zepto Adapters +* Added Core Library Unit Testing +* New `domain` and `range` options for basic charts + +### Bug Fixes + +* Event `.off` method was completely busted, fixed +* Swapped terminology for horizontal and vertical bar plots +* Removed `isVisible` and related rendering hacks (caused all sorts of woe) + + +## 0.5.2 - June 24th, 2014 + +### Enhancements / Features + +* #36 - Fixed the readme to focus on development +* #54 - Added vertical orientation option to the basic bar chart + +## 0.5.1 - June 23rd, 2014 + +### Bug Fixes + +* #52 - Replaced instances of `$` with `jQuery` (ambiguous, otherwise) + +## 0.5.0 - June 23rd, 2014 + +### Enhancements / Features + +* #32 - QueryCSS greatly enhanced - now builds a full DOM context when computing styles +* #42 - Heat map now allows for painting of "zero" values via a new `paintZeroValues` option +* #43 - Heat map color computation abstracted out of `_paintEntry` (makes it easier to extend) + +### Bug Fixes + +* #22 - Fixed an issue with pie chart transitions +* #30 - Layers without labels now correctly render on a various basic charts +* #31 - Real-time Line Chart thickness fixed by taking pixel ratio into account +* #41 - Fixed bucketing issues with the Heat Map +* #46 - Removed default black stroke from the Real-Time Area chart diff --git a/debian/missing-sources/epoch/LICENSE b/debian/missing-sources/epoch/LICENSE new file mode 100644 index 0000000..3e8d8f8 --- /dev/null +++ b/debian/missing-sources/epoch/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Fastly, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/debian/missing-sources/epoch/README.md b/debian/missing-sources/epoch/README.md new file mode 100644 index 0000000..5095f3a --- /dev/null +++ b/debian/missing-sources/epoch/README.md @@ -0,0 +1,135 @@ +## Epoch +By Ryan Sandor Richards + +[![Build Status](https://travis-ci.org/epochjs/epoch.svg?branch=master)](https://travis-ci.org/epochjs/epoch) +[![Dependency Status](https://david-dm.org/epochjs/epoch.svg)](https://david-dm.org/epochjs/epoch) +[![devDependency Status](https://david-dm.org/epochjs/epoch/dev-status.svg)](https://david-dm.org/epochjs/epoch#info=devDependencies) + +Epoch is a general purpose charting library for application developers and visualization designers. It focuses on two different aspects of visualization programming: **basic charts** for creating historical reports, and **real-time charts** for displaying frequently updating timeseries data. + +To get started using Epoch, please refer to the [Epoch Project Site](http://epochjs.github.io/epoch/). There you can find full documentation and guides to help you start using Epoch right away. + +### Installation +Epoch can be easily installed via the following package managers: + +* [npm](https://www.npmjs.com/package/epoch-charting) +* [bower](http://bower.io/search/?q=epoch) +* [packagist](https://packagist.org/packages/epochjs/epoch) + +If you don't see your favorite package manager in the list above feel free to +[open up an issue](https://github.com/epochjs/epoch/issues/new) and let us know. +Finally, you can download any release of the library from the +[project releases page](https://github.com/epochjs/epoch/releases). + +**Important:** Epoch requires [d3](https://github.com/mbostock/d3). In order to +work properly your page must load d3 before epoch. + +#### Public CDN URLs +If you don't want to host the files yourself, you can use +[jsDelivr](http://www.jsdelivr.com/) to serve the files: + +1. Visit [epoch page on jsDelvr](http://www.jsdelivr.com/projects/epoch). +2. Copy the provided URL's and link to them in your project. + +### Developing Epoch + +Developing Epoch is a reasonably straight forward process. In this section we'll +cover the basic on how to develop Epoch by detailing common build task, exploring +how the source is arranged, and finally show how to use rendering tests to aid +development. + +#### Configuring Development Environment + +Epoch requires the following for development: + +1. [Node.js](https://nodejs.org/en/) (v4.1.1+) +2. [NPM](https://www.npmjs.com/) (v2.1.0+) + +Once both are installed on your machine you will need to run `npm install` from +the repository's root directory in order to install the npm packages required +to develop epoch. + +Once you have installed the required npm packages you can use `gulp build` to +fully rebuild the source (see more information about gulp tasks below). + + +#### Basic Development Process + +The best way to start contributing to Epoch is to follow these steps: + +1. Change to the source directory for the project +2. Run `gulp watch` to recompile the project after source files change +3. Make changes in a source file (either in `src/` or `sass/`) +4. In a web browser open the `test/index.html` and browse the rendering tests +5. Use the rendering tests to see if your changes had the desired result +6. Ensure unit tests with pass `npm test` + +#### Testing + +Epoch uses two types of testing to ensure that changes do not cause unintended +side effects. The first, unit tests, ensure that the core functional components +of the library work as expected. The second, rendering tests, allow you to +ensure that charts and graphs are correctly rendered. + +It is important to keep both unit test and rendering tests up-to-date! When +developing, use the following guidelines: + +* When adding new features make sure to add new tests +* When changing existing functionality, ensure that the appropriate both types + of tests still pass +* If you want to make a new type of chart, add a whole new test suite for that + chart! + +Keeping the tests current makes it easier for others to review your code and +spot issues. Also, pull requests without appropriate testing will not be +merged. + + +#### Gulp Tasks + +Epoch uses [gulp](https://github.com/gulpjs/gulp) to perform various tasks. The +`gulpfile.js` file defines the following tasks: + +* `gulp clean` - Cleans the `dist/` directory. +* `gulp build` - Builds the CoffeeScript and Sass source into the `dist/` + directory. +* `gulp watch` - Starts a watch script to recompile CoffeeScript and Sass when + any files change. + +#### Source Structure + +The directory structure for the Epoch project follows some basic guidelines, here's an overview of how it is structured: + +``` +dist/ - Compiled JavaScript and CSS source +src/ - Main source directory + core/ - Core Epoch Library Files + util.coffee - Library Utility Routines + d3.coffee - d3 Extensions + format.coffee - Data formatters + chart.coffee - Base Chart Classes + css.coffee - CSS Querying Engine + adapters/ - 3rd Party Library Adapters (currently only jQuery) + basic/ - Basic Chart Classes + time/ - Real-time Chart Classes + adapters.coffee - Options / Global Classes for Adapter Implementations + basic.coffee - Base Classes for Basic Charts + data.coffee - Data Formatting + epoch.coffee - Main source file, defines name spaces, etc. + model.coffee - Data Model + time.coffee - Base Classes for Real-Time Charts +sass/ - Scss source for the default epoch stylesheet +tests/ + render/ - Rendering tests + basic/ - Basic chart rendering tests + real-time/ - Real-time rendering tests + unit/ - Unit tests +``` + +### Release Checklist + +- Run `npm test` and ensure all tests pass +- Run `npm version [major|minor|patch]` +- Run `npm publish` +- Update CHANGELOG.md with the changes since last release +- Update the `gh-pages` branch's library version in `_config.yml` diff --git a/debian/missing-sources/epoch/bower.json b/debian/missing-sources/epoch/bower.json new file mode 100644 index 0000000..36ee8e6 --- /dev/null +++ b/debian/missing-sources/epoch/bower.json @@ -0,0 +1,37 @@ +{ + "name": "epoch", + "description": "A general purpose, real-time visualization library.", + "main": [ + "dist/js/epoch.min.js", + "dist/css/epoch.min.css" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "/src", + "/sass", + "/tests", + "/Cakefile", + "/CHANGELOG.md", + "/package.json" + ], + "authors": [ + "Ryan Sandor Richards <sandor.richards@gmail.com>" + ], + "keywords": [ + "fastly", + "realtime", + "graph", + "chart", + "stats", + "visualization" + ], + "homepage": "https://fastly.github.io/epoch/", + "repository": { + "type": "git", + "url": "git://github.com/fastly/epoch.git" + }, + "dependencies": { + "d3": "^3.4.13" + } +} diff --git a/debian/missing-sources/epoch/composer.json b/debian/missing-sources/epoch/composer.json new file mode 100644 index 0000000..184efe0 --- /dev/null +++ b/debian/missing-sources/epoch/composer.json @@ -0,0 +1,22 @@ +{ + "name": "epochjs/epoch", + "description": "A general purpose, real-time visualization library.", + "keywords": [ + "epoch", + "d3", + "chart", + "graph", + "plot", + "real-time" + ], + "homepage": "https://github.com/epochjs/epoch", + "license": "MIT", + "authors": [ + { + "name": "Ryan Sandor Richards" + } + ], + "require": { + "mbostock/d3": "@stable" + } +} diff --git a/debian/missing-sources/epoch/gulpfile.js b/debian/missing-sources/epoch/gulpfile.js new file mode 100644 index 0000000..4f3b975 --- /dev/null +++ b/debian/missing-sources/epoch/gulpfile.js @@ -0,0 +1,108 @@ +'use strict'; + +require('coffee-script/register'); // For coffee-script mocha unit tests + +var gulp = require('gulp'); +var coffee = require('gulp-coffee'); +var concat = require('gulp-concat'); +var mocha = require('gulp-mocha'); +var order = require('gulp-order'); +var rename = require('gulp-rename'); +var sass = require('gulp-sass'); +var uglify = require('gulp-uglify'); +var gutil = require('gulp-util'); +var del = require('del'); +var exec = require('child_process').exec; + +/** + * Common directories used by tasks below. + * @type {object} + */ +var path = { + source: { + coffee: 'src/', + sass: 'sass/' + }, + dist: { + js: 'dist/js/', + css: 'dist/css/' + }, + test: { + unit: 'tests/unit/' + }, + doc: 'doc/' +}; + +/** + * The default task simply calls the master 'build' task. + */ +gulp.task('default', ['build']); + +/** + * Builds the distribution files by packaging the compiled javascript source + * into the `dist/js/` directory and building the css into the `dist/css` + * directory + */ +gulp.task('build', ['sass', 'sass-minify'], function () { + gulp.src(path.source.coffee + '**/*.coffee') + .pipe(coffee({bare: true}).on('error', gutil.log)) + .pipe(order([ + 'epoch.js', + 'core/context.js', + 'core/util.js', + 'core/d3.js', + 'core/format.js', + 'core/chart.js', + 'core/css.js', + 'data.js', + 'model.js', + 'basic.js', + 'basic/*.js', + 'time.js', + 'time/*.js', + 'adapters.js', + 'adapters/*.js' + ])) + .pipe(concat('epoch.js')) + .pipe(gulp.dest(path.dist.js)) + .pipe(uglify().on('error', gutil.log)) + .pipe(rename('epoch.min.js')) + .pipe(gulp.dest(path.dist.js)); +}); + +/** + * Generates epoch CSS from Sass source. + */ +gulp.task('sass', function () { + gulp.src(path.source.sass + 'epoch.scss') + .pipe(sass({ outputStyle: 'compact' })) + .pipe(rename('epoch.css')) + .pipe(gulp.dest(path.dist.css)); +}); + +/** + * Generates the minified version of the epoch css from sass source. + */ +gulp.task('sass-minify', function () { + gulp.src(path.source.sass + 'epoch.scss') + .pipe(sass({ outputStyle: 'compressed' })) + .pipe(rename('epoch.min.css')) + .pipe(gulp.dest(path.dist.css)); +}); + +/** + * Watch script for recompiling JavaScript and CSS + */ +gulp.task('watch', function () { + gulp.watch(path.source.coffee + '**/*.coffee', ['build']); + gulp.watch(path.source.sass + '**/*.scss', ['sass', 'sass-minify']); +}); + +/** + * Cleans all build and distribution files. + */ +gulp.task('clean', function (cb) { + del([ path.dist.js, path.dist.css]).then(function () { + cb(); + }); +}); diff --git a/debian/missing-sources/epoch/package.json b/debian/missing-sources/epoch/package.json new file mode 100644 index 0000000..80f14ef --- /dev/null +++ b/debian/missing-sources/epoch/package.json @@ -0,0 +1,55 @@ +{ + "name": "epoch-charting", + "version": "0.8.4", + "description": "A general purpose real-time charting library for building beautiful, smooth, and high performance visualizations.", + "keywords": [ + "chart", + "charting", + "visualization", + "svg", + "animation", + "canvas", + "d3" + ], + "homepage": "http://fastly.github.io/epoch/", + "author": { + "name": "rsandor", + "url": "https://github.com/rsandor" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/fastly/epoch.git" + }, + "main": "dist/js/epoch.js", + "dependencies": { + "d3": "^3.4.13" + }, + "devDependencies": { + "chai": "^3.3.0", + "codo": "^2.0.11", + "coffee-script": "^1.10.0", + "del": "^2.0.2", + "gulp": "^3.9.0", + "gulp-coffee": "^2.3.1", + "gulp-concat": "^2.6.0", + "gulp-mocha": "^2.1.3", + "gulp-order": "^1.1.1", + "gulp-rename": "^1.2.2", + "gulp-sass": "^2.0.4", + "gulp-uglify": "^1.4.1", + "gulp-util": "^3.0.6", + "jsdom": "^7.0.2", + "mocha": "^2.3.3", + "node-minify": "^1.2.1", + "node-sass": "^3.3.3", + "sinon": "^1.17.1", + "xmlhttprequest": "^1.7.0" + }, + "scripts": { + "build": "gulp build", + "unit": "mocha --recursive --compilers coffee:coffee-script/register tests/unit/", + "test": "npm run build && npm run unit", + "codo": "./node_modules/.bin/codo --quiet --private --name Epoch --readme README.md --title 'Epoch Documentation' --output codo-doc src - LICENSE" + } +} diff --git a/debian/missing-sources/epoch/sass/_core.scss b/debian/missing-sources/epoch/sass/_core.scss new file mode 100644 index 0000000..93bc767 --- /dev/null +++ b/debian/missing-sources/epoch/sass/_core.scss @@ -0,0 +1,99 @@ +/* + * Core Epoch Styles + */ + +/** + * Generates the styles needed to define a fill color for a given class name. + * @param $name Name of the class to use (sans the leading .) + * @param $color Fill color to associate with the class name. + */ +@mixin epoch-color($name, $color) { + div.ref.#{$name} { + background-color: $color; + } + .#{$name} { + .line { stroke: $color; } + .area, .dot { fill: $color; } + } + .arc.#{$name} path { + fill: $color; + } + .bar.#{$name} { + fill: $color; + } + .#{$name} { + .bucket { fill: $color; } + } +} + +/** + * Produces categorical color classes for plots (excluding heatmaps). + * @param $list List of colors to use for each category. + * @param $entries Size of the input list. + */ +@mixin epoch-category-colors($list, $entries) { + @for $i from 1 through $entries { + div.ref.category#{$i} { + background-color: nth($list, $i); + } + .category#{$i} { + .line { stroke: nth($list, $i); } + .area, .dot { fill: nth($list, $i); stroke: rgba(0,0,0,0); } + } + .arc.category#{$i} path { + fill: nth($list, $i); + } + .bar.category#{$i} { + fill: nth($list, $i); + } + } +} + +/** + * Produces categorical colors for heatmaps. + * @param $list List of colors to use for categories. + * @param $entries Size of the input list. + */ +@mixin epoch-heatmap-colors($list, $entries) { + @for $i from 1 through $entries { + .category#{$i} { + .bucket { fill: nth($list, $i); } + } + } +} + +// Axis and Tick Shape Rendering +.epoch { + .axis path, .axis line { + shape-rendering: crispEdges; + } + + .axis.canvas .tick line { + shape-rendering: geometricPrecision; + } +} + +/* + * Canvas Styles Reference Container + * + * The reference container is an SVG that is automatically created when Epoch is loaded. It is used + * by the canvas based plots to obtain color information from the page styles by creating reference + * elements and then reading their computed styles. + * + * Note: don't mess with this ;) + */ +div#_canvas_css_reference { + width: 0; + height: 0; + position: absolute; + top: -1000px; + left: -1000px; + svg { + position: absolute; + width: 0; + height: 0; + top: -1000px; + left: -1000px; + } +} + diff --git a/debian/missing-sources/epoch/sass/epoch.scss b/debian/missing-sources/epoch/sass/epoch.scss new file mode 100644 index 0000000..2689262 --- /dev/null +++ b/debian/missing-sources/epoch/sass/epoch.scss @@ -0,0 +1,12 @@ +/* + * Epoch Master SCSS + * + * Includes the core styles and all the themes to produce the complete epoch css file. + * + * By Ryan Sandor Richards + * Copyright 2013 Fastly, Inc. + */ + +@import "core"; +@import "themes/default"; +@import "themes/dark"; diff --git a/debian/missing-sources/epoch/sass/themes/_dark.scss b/debian/missing-sources/epoch/sass/themes/_dark.scss new file mode 100644 index 0000000..e343eff --- /dev/null +++ b/debian/missing-sources/epoch/sass/themes/_dark.scss @@ -0,0 +1,95 @@ +/* + * theme/_dark.scss - Theme design for dark page backgrounds. + * Designed by Ryan Sandor Richards + */ + +$axisAndText: #d0d0d0; +$background: #333; + +.epoch-theme-dark { + // Axes and Ticks + .epoch { + .axis path, .axis line { + stroke: $axisAndText; + } + .axis .tick text { + fill: $axisAndText; + } + } + + // Pie Charts + .arc.pie { + stroke: $background; + } + + .arc.pie text { + fill: $background; + } + + // Gauges + .epoch .gauge-labels .value { + fill: #BBB; + } + + .epoch .gauge { + .arc.outer { + stroke: #999; + } + .arc.inner { + stroke: #AAA; + } + .tick { + stroke: #AAA; + } + + .needle { + fill: #F3DE88; + } + + .needle-base { + fill: #999; + } + } + + // Categorical Colors + $dark_category10: + #909CFF, #FFAC89, #E889E8, #78E8D3, #C2FF97, + #B7BCD1, #FF857F, #F3DE88, #C9935E, #A488FF; + + .epoch, .epoch.category10 { + @include epoch-category-colors($dark_category10, 10); + } + + $dark_category20: + #909CFF, #626AAD, #FFAC89, #BD7F66, + #E889E8, #995A99, #78E8D3, #4F998C, + #C2FF97, #789E5E, #B7BCD1, #7F8391, + #CCB889, #A1906B, #F3DE88, #A89A5E, + #FF857F, #BA615D, #A488FF, #7662B8; + + .epoch.category20 { + @include epoch-category-colors($dark_category20, 20); + } + + $dark_category20b: + #909CFF, #7680D1, #656DB2, #525992, + #FFAC89, #D18D71, #AB735C, #92624E, + #E889E8, #BA6EBA, #9B5C9B, #7B487B, + #78E8D3, #60BAAA, #509B8D, #3F7B70, + #C2FF97, #9FD17C, #7DA361, #65854E; + + .epoch.category20b { + @include epoch-category-colors($dark_category20b, 20); + } + + $dark_category20c: + #B7BCD1, #979DAD, #6E717D, #595C66, + #FF857F, #DE746E, #B55F5A, #964E4B, + #F3DE88, #DBC87B, #BAAA68, #918551, + #C9935E, #B58455, #997048, #735436, + #A488FF, #8670D1, #705CAD, #52447F; + + .epoch.category20c { + @include epoch-category-colors($dark_category20c, 20); + } +} diff --git a/debian/missing-sources/epoch/sass/themes/_default.scss b/debian/missing-sources/epoch/sass/themes/_default.scss new file mode 100644 index 0000000..d3075c8 --- /dev/null +++ b/debian/missing-sources/epoch/sass/themes/_default.scss @@ -0,0 +1,189 @@ +/* + * theme/_default.scss - Default Color Theme + * Categorical Colors Adapted from d3: + * https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors + */ + +// Fonts +.epoch { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12pt; +} + +// Axes and Ticks +.epoch { + .axis path, .axis line { + fill: transparent; + stroke: #000; + } + + .axis .tick text { + font-size: 9pt; + } +} + +// Line Charts +.epoch .line { + fill: transparent; + stroke-width: 2px; +} + +.epoch.sparklines .line { + stroke-width: 1px; +} + + +// Area Charts +.epoch .area { + stroke: transparent; +} + +// Pie Charts +.epoch { + .arc.pie { + stroke: #fff; + stroke-width: 1.5px; + } + + .arc.pie text { + stroke: transparent; + fill: white; + font-size: 9pt; + } +} + +// Gauge Charts +.epoch .gauge-labels { + .value { + text-anchor: middle; + font-size: 140%; + fill: #666; + } +} + +.epoch.gauge-tiny { + width: 120px; height: 90px; + + .gauge-labels { + .value { font-size: 80%; } + } + + .gauge { + .arc.outer { + stroke-width: 2px; + } + } +} + +.epoch.gauge-small { + width: 180px; height: 135px; + + .gauge-labels { + .value { font-size: 120%; } + } + + .gauge { + .arc.outer { + stroke-width: 3px; + } + } +} + +.epoch.gauge-medium { + width: 240px; height: 180px; + + .gauge { + .arc.outer { + stroke-width: 3px; + } + } +} + +.epoch.gauge-large { + width: 320px; height: 240px; + .gauge-labels { + .value { font-size: 180%; } + } +} + +.epoch .gauge { + .arc.outer { + stroke-width: 4px; + stroke: #666; + } + .arc.inner { + stroke-width: 1px; + stroke: #555; + } + .tick { + stroke-width: 1px; + stroke: #555; + } + + .needle { + fill: orange; + } + + .needle-base { + fill: #666; + } +} + +// Categorical Colors +$category10: + #1f77b4, #ff7f0e, #2ca02c, #d62728, #9467bd, + #8c564b, #e377c2, #7f7f7f, #bcbd22, #17becf; + +.epoch, .epoch.category10 { + @include epoch-category-colors($category10, 10); +} + +$category20: + #1f77b4, #aec7e8, #ff7f0e, #ffbb78, #2ca02c, + #98df8a, #d62728, #ff9896, #9467bd, #c5b0d5, + #8c564b, #c49c94, #e377c2, #f7b6d2, #7f7f7f, + #c7c7c7, #bcbd22, #dbdb8d, #17becf, #9edae5; + +.epoch.category20 { + @include epoch-category-colors($category20, 20); +} + +$category20b: + #393b79, #5254a3, #6b6ecf, #9c9ede, #637939, + #8ca252, #b5cf6b, #cedb9c, #8c6d31, #bd9e39, + #e7ba52, #e7cb94, #843c39, #ad494a, #d6616b, + #e7969c, #7b4173, #a55194, #ce6dbd, #de9ed6; + +.epoch.category20b { + @include epoch-category-colors($category20b, 20); +} + +$category20c: + #3182bd, #6baed6, #9ecae1, #c6dbef, + #e6550d, #fd8d3c, #fdae6b, #fdd0a2, + #31a354, #74c476, #a1d99b, #c7e9c0, + #756bb1, #9e9ac8, #bcbddc, #dadaeb, + #636363, #969696, #bdbdbd, #d9d9d9; + +.epoch.category20c { + @include epoch-category-colors($category20c, 20); +} + + +/* + * Heatmap Colors + * + * The heatmap real-time graph uses color blending to choose the color for which to render each bucket. + * Basic d3 categorical colors do not work well in this instance because the colors in the sequence + * vary wildly in precieved luminosity. + * + * Below we define small subsets that should work well together because they are of similar luminosities. + * Note: darker colors work better since we use opacity to denote "height" or "intensity" for each bucket + * after picking a mix color. + */ +$heatmap5: + #1f77b4, #2ca02c, #d62728, #8c564b, #7f7f7f; + +.epoch, .epoch.heatmap5 { + @include epoch-heatmap-colors($heatmap5, 5); +} diff --git a/debian/missing-sources/epoch/src/adapters.coffee b/debian/missing-sources/epoch/src/adapters.coffee new file mode 100644 index 0000000..e378af5 --- /dev/null +++ b/debian/missing-sources/epoch/src/adapters.coffee @@ -0,0 +1,13 @@ +# Maps short string names to classes for library adapters. +Epoch._typeMap = + 'area': Epoch.Chart.Area + 'bar': Epoch.Chart.Bar + 'line': Epoch.Chart.Line + 'pie': Epoch.Chart.Pie + 'scatter': Epoch.Chart.Scatter + 'histogram': Epoch.Chart.Histogram + 'time.area': Epoch.Time.Area + 'time.bar': Epoch.Time.Bar + 'time.line': Epoch.Time.Line + 'time.gauge': Epoch.Time.Gauge + 'time.heatmap': Epoch.Time.Heatmap diff --git a/debian/missing-sources/epoch/src/adapters/MooTools.coffee b/debian/missing-sources/epoch/src/adapters/MooTools.coffee new file mode 100644 index 0000000..8b3d544 --- /dev/null +++ b/debian/missing-sources/epoch/src/adapters/MooTools.coffee @@ -0,0 +1,19 @@ +MooToolsModule = -> + # Data key to use for storing a reference to the chart instance on an element. + DATA_NAME = 'epoch-chart' + + # Adds an Epoch chart of the given type to the referenced element. + # @param [Object] options Options for the chart. + # @option options [String] type The type of chart to append to the referenced element. + # @return [Object] The chart instance that was associated with the containing element. + Element.implement 'epoch', (options) -> + self = $$(this) + unless (chart = self.retrieve(DATA_NAME)[0])? + options.el = this + klass = Epoch._typeMap[options.type] + unless klass? + Epoch.exception "Unknown chart type '#{options.type}'" + self.store DATA_NAME, (chart = new klass options) + return chart + +MooToolsModule() if window.MooTools? diff --git a/debian/missing-sources/epoch/src/adapters/jQuery.coffee b/debian/missing-sources/epoch/src/adapters/jQuery.coffee new file mode 100644 index 0000000..e115bad --- /dev/null +++ b/debian/missing-sources/epoch/src/adapters/jQuery.coffee @@ -0,0 +1,18 @@ +jQueryModule = ($) -> + # Data key to use for storing a reference to the chart instance on an element. + DATA_NAME = 'epoch-chart' + + # Adds an Epoch chart of the given type to the referenced element. + # @param [Object] options Options for the chart. + # @option options [String] type The type of chart to append to the referenced element. + # @return [Object] The chart instance that was associated with the containing element. + $.fn.epoch = (options) -> + options.el = @get(0) + unless (chart = @data(DATA_NAME))? + klass = Epoch._typeMap[options.type] + unless klass? + Epoch.exception "Unknown chart type '#{options.type}'" + @data DATA_NAME, (chart = new klass options) + return chart + +jQueryModule(jQuery) if window.jQuery? diff --git a/debian/missing-sources/epoch/src/adapters/zepto.coffee b/debian/missing-sources/epoch/src/adapters/zepto.coffee new file mode 100644 index 0000000..81153dc --- /dev/null +++ b/debian/missing-sources/epoch/src/adapters/zepto.coffee @@ -0,0 +1,27 @@ +zeptoModule = ($) -> + # For mapping charts to selected elements + DATA_NAME = 'epoch-chart' + chartMap = {} + chartId = 0 + next_cid = -> "#{DATA_NAME}-#{++chartId}" + + # Adds an Epoch chart of the given type to the referenced element. + # @param [Object] options Options for the chart. + # @option options [String] type The type of chart to append to the referenced element. + # @return [Object] The chart instance that was associated with the containing element. + $.extend $.fn, + epoch: (options) -> + return chartMap[cid] if (cid = @data(DATA_NAME))? + options.el = @get(0) + + klass = Epoch._typeMap[options.type] + unless klass? + Epoch.exception "Unknown chart type '#{options.type}'" + + @data DATA_NAME, (cid = next_cid()) + chart = new klass options + chartMap[cid] = chart + + return chart + +zeptoModule(Zepto) if window.Zepto? diff --git a/debian/missing-sources/epoch/src/basic.coffee b/debian/missing-sources/epoch/src/basic.coffee new file mode 100644 index 0000000..8af3465 --- /dev/null +++ b/debian/missing-sources/epoch/src/basic.coffee @@ -0,0 +1,243 @@ +# Base class for all two-dimensional basic d3 charts. This class handles axes and +# margins so that subclasses can focus on the construction of particular chart +# types. +class Epoch.Chart.Plot extends Epoch.Chart.SVG + defaults = + domain: null, + range: null, + axes: ['left', 'bottom'] + ticks: + top: 14 + bottom: 14 + left: 5 + right: 5 + tickFormats: + top: Epoch.Formats.regular + bottom: Epoch.Formats.regular + left: Epoch.Formats.si + right: Epoch.Formats.si + + defaultAxisMargins = + top: 25 + right: 50 + bottom: 25 + left: 50 + + optionListeners = + 'option:margins.top': 'marginsChanged' + 'option:margins.right': 'marginsChanged' + 'option:margins.bottom': 'marginsChanged' + 'option:margins.left': 'marginsChanged' + 'option:axes': 'axesChanged' + 'option:ticks.top': 'ticksChanged' + 'option:ticks.right': 'ticksChanged' + 'option:ticks.bottom': 'ticksChanged' + 'option:ticks.left': 'ticksChanged' + 'option:tickFormats.top': 'tickFormatsChanged' + 'option:tickFormats.right': 'tickFormatsChanged' + 'option:tickFormats.bottom': 'tickFormatsChanged' + 'option:tickFormats.left': 'tickFormatsChanged' + 'option:domain': 'domainChanged' + 'option:range': 'rangeChanged' + + # Creates a new plot chart. + # @param [Object] options Options to use when constructing the plot. + # @option options [Object] margins For setting explicit values for the top, + # right, bottom, and left margins in the visualization. Normally these can + # be omitted and the class will set appropriately sized margins given which + # axes are specified. + # @option options [Array] axes A list of axes to display (top, left, bottom, right). + # @option options [Object] ticks Number of ticks to place on the top, left bottom + # and right axes. + # @option options [Object] tickFormats What tick formatting functions to use for + # the top, bottom, left, and right axes. + constructor: (@options={}) -> + givenMargins = Epoch.Util.copy(@options.margins) or {} + super(@options = Epoch.Util.defaults(@options, defaults)) + + # Margins are used in a special way and only for making room for axes. + # However, a user may explicitly set margins in the options, so we need + # to determine if they did so, and zero out the ones they didn't if no + # axis is present. + @margins = {} + for pos in ['top', 'right', 'bottom', 'left'] + @margins[pos] = if @options.margins? and @options.margins[pos]? + @options.margins[pos] + else if @hasAxis(pos) + defaultAxisMargins[pos] + else + 6 + + # Add a translation for the top and left margins + @g = @svg.append("g") + .attr("transform", "translate(#{@margins.left}, #{@margins.top})") + + # Register option change events + @onAll optionListeners + + # Sets the tick formatting function to use on the given axis. + # @param [String] axis Name of the axis. + # @param [Function] fn Formatting function to use. + setTickFormat: (axis, fn) -> + @options.tickFormats[axis] = fn + + # @return [Boolean] <code>true</code> if the chart has an axis with a given name, <code>false</code> otherwise. + # @param [String] axis Name of axis to check. + hasAxis: (axis) -> + @options.axes.indexOf(axis) > -1 + + # @return [Number] Width of the visualization portion of the chart (width - margins). + innerWidth: -> + @width - (@margins.left + @margins.right) + + # @return [Number] Height of the visualization portion of the chart (height - margins). + innerHeight: -> + @height - (@margins.top + @margins.bottom) + + # @return [Function] The x scale for the visualization. + x: -> + domain = @options.domain ? @extent((d) -> d.x) + d3.scale.linear() + .domain(domain) + .range([0, @innerWidth()]) + + # @return [Function] The y scale for the visualization. + y: (givenDomain) -> + d3.scale.linear() + .domain(@_getScaleDomain(givenDomain)) + .range([@innerHeight(), 0]) + + # @return [Function] d3 axis to use for the bottom of the visualization. + bottomAxis: -> + d3.svg.axis().scale(@x()).orient('bottom') + .ticks(@options.ticks.bottom) + .tickFormat(@options.tickFormats.bottom) + + # @return [Function] d3 axis to use for the top of the visualization. + topAxis: -> + d3.svg.axis().scale(@x()).orient('top') + .ticks(@options.ticks.top) + .tickFormat(@options.tickFormats.top) + + # @return [Function] d3 axis to use on the left of the visualization. + leftAxis: -> + range = if @options.range then @options.range.left else null + d3.svg.axis().scale(@y(range)).orient('left') + .ticks(@options.ticks.left) + .tickFormat(@options.tickFormats.left) + + # @return [Function] d3 axis to use on the right of the visualization. + rightAxis: -> + range = if @options.range then @options.range.right else null + d3.svg.axis().scale(@y(range)).orient('right') + .ticks(@options.ticks.right) + .tickFormat(@options.tickFormats.right) + + # Renders the axes for the visualization (subclasses must implement specific + # drawing routines). + draw: -> + if @_axesDrawn + @_redrawAxes() + else + @_drawAxes() + super() + + # Redraws the axes for the visualization. + _redrawAxes: -> + if @hasAxis('bottom') + @g.selectAll('.x.axis.bottom').transition() + .duration(500) + .ease('linear') + .call(@bottomAxis()) + if @hasAxis('top') + @g.selectAll('.x.axis.top').transition() + .duration(500) + .ease('linear') + .call(@topAxis()) + if @hasAxis('left') + @g.selectAll('.y.axis.left').transition() + .duration(500) + .ease('linear') + .call(@leftAxis()) + if @hasAxis('right') + @g.selectAll('.y.axis.right').transition() + .duration(500) + .ease('linear') + .call(@rightAxis()) + + # Draws the initial axes for the visualization. + _drawAxes: -> + if @hasAxis('bottom') + @g.append("g") + .attr("class", "x axis bottom") + .attr("transform", "translate(0, #{@innerHeight()})") + .call(@bottomAxis()) + if @hasAxis('top') + @g.append("g") + .attr('class', 'x axis top') + .call(@topAxis()) + if @hasAxis('left') + @g.append("g") + .attr("class", "y axis left") + .call(@leftAxis()) + if @hasAxis('right') + @g.append('g') + .attr('class', 'y axis right') + .attr('transform', "translate(#{@innerWidth()}, 0)") + .call(@rightAxis()) + @_axesDrawn = true + + dimensionsChanged: -> + super() + @g.selectAll('.axis').remove() + @_axesDrawn = false + @draw() + + # Updates margins in response to a <code>option:margin.*</code> event. + marginsChanged: -> + return unless @options.margins? + for own pos, size of @options.margins + unless size? + @margins[pos] = 6 + else + @margins[pos] = size + + @g.transition() + .duration(750) + .attr("transform", "translate(#{@margins.left}, #{@margins.top})") + + @draw() + + # Updates axes in response to a <code>option:axes</code> event. + axesChanged: -> + # Remove default axis margins + for pos in ['top', 'right', 'bottom', 'left'] + continue if @options.margins? and @options.margins[pos]? + if @hasAxis(pos) + @margins[pos] = defaultAxisMargins[pos] + else + @margins[pos] = 6 + + # Update the margin offset + @g.transition() + .duration(750) + .attr("transform", "translate(#{@margins.left}, #{@margins.top})") + + # Remove the axes and redraw + @g.selectAll('.axis').remove() + @_axesDrawn = false + @draw() + + # Updates ticks in response to a <code>option:ticks.*</code> event. + ticksChanged: -> @draw() + + # Updates tick formats in response to a <code>option:tickFormats.*</code> event. + tickFormatsChanged: -> @draw() + + # Updates chart in response to a <code>option:domain</code> event. + domainChanged: -> @draw() + + # Updates chart in response to a <code>option:range</code> event. + rangeChanged: -> @draw() + +# "They will see us waving from such great heights, come down now..." - The Postal Service diff --git a/debian/missing-sources/epoch/src/basic/area.coffee b/debian/missing-sources/epoch/src/basic/area.coffee new file mode 100644 index 0000000..e44b480 --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/area.coffee @@ -0,0 +1,51 @@ + +# Static stacked area chart implementation using d3. +class Epoch.Chart.Area extends Epoch.Chart.Plot + constructor: (@options={}) -> + @options.type ?= 'area' + super(@options) + @draw() + + # Generates a scale needed to appropriately render the stacked visualization. + # @return [Function] The y scale for the visualization. + y: -> + a = [] + for layer in @getVisibleLayers() + for own k, v of layer.values + a[k] += v.y if a[k]? + a[k] = v.y unless a[k]? + d3.scale.linear() + .domain(@options.range ? [0, d3.max(a)]) + .range([@height - @margins.top - @margins.bottom, 0]) + + # Renders the SVG elements needed to display the stacked area chart. + draw: -> + [x, y, layers] = [@x(), @y(), @getVisibleLayers()] + + @g.selectAll('.layer').remove() + return if layers.length == 0 + + area = d3.svg.area() + .x((d) -> x(d.x)) + .y0((d) -> y(d.y0)) + .y1((d) -> y(d.y0 + d.y)) + + stack = d3.layout.stack() + .values((d) -> d.values) + + data = stack layers + + layer = @g.selectAll('.layer') + .data(layers, (d) -> d.category) + + layer.select('.area') + .attr('d', (d) -> area(d.values)) + + layer.enter().append('g') + .attr('class', (d) -> d.className) + + layer.append('path') + .attr('class', 'area') + .attr('d', (d) -> area(d.values)) + + super() diff --git a/debian/missing-sources/epoch/src/basic/bar.coffee b/debian/missing-sources/epoch/src/basic/bar.coffee new file mode 100644 index 0000000..8fbc427 --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/bar.coffee @@ -0,0 +1,273 @@ +# Static bar chart implementation (using d3). +class Epoch.Chart.Bar extends Epoch.Chart.Plot + defaults = + type: 'bar' + style: 'grouped' + orientation: 'vertical' + padding: + bar: 0.08 + group: 0.1 + outerPadding: + bar: 0.08 + group: 0.1 + + horizontal_specific = + tickFormats: + top: Epoch.Formats.si + bottom: Epoch.Formats.si + left: Epoch.Formats.regular + right: Epoch.Formats.regular + + horizontal_defaults = Epoch.Util.defaults(horizontal_specific, defaults) + + optionListeners = + 'option:orientation': 'orientationChanged' + 'option:padding': 'paddingChanged' + 'option:outerPadding': 'paddingChanged' + 'option:padding:bar': 'paddingChanged' + 'option:padding:group': 'paddingChanged' + 'option:outerPadding:bar': 'paddingChanged' + 'option:outerPadding:group': 'paddingChanged' + + constructor: (@options={}) -> + if @_isHorizontal() + @options = Epoch.Util.defaults(@options, horizontal_defaults) + else + @options = Epoch.Util.defaults(@options, defaults) + super(@options) + @onAll optionListeners + @draw() + + # @return [Boolean] True if the chart is vertical, false otherwise + _isVertical: -> + @options.orientation == 'vertical' + + # @return [Boolean] True if the chart is horizontal, false otherwise + _isHorizontal: -> + @options.orientation == 'horizontal' + + # @return [Function] The scale used to generate the chart's x scale. + x: -> + if @_isVertical() + d3.scale.ordinal() + .domain(Epoch.Util.domain(@getVisibleLayers())) + .rangeRoundBands([0, @innerWidth()], @options.padding.group, @options.outerPadding.group) + else + extent = @extent((d) -> d.y) + extent[0] = Math.min(0, extent[0]) + d3.scale.linear() + .domain(extent) + .range([0, @width - @margins.left - @margins.right]) + + # @return [Function] The x scale used to render the horizontal bar chart. + x1: (x0) -> + d3.scale.ordinal() + .domain((layer.category for layer in @getVisibleLayers())) + .rangeRoundBands([0, x0.rangeBand()], @options.padding.bar, @options.outerPadding.bar) + + # @return [Function] The y scale used to render the bar chart. + y: -> + if @_isVertical() + extent = @extent((d) -> d.y) + extent[0] = Math.min(0, extent[0]) + d3.scale.linear() + .domain(extent) + .range([@height - @margins.top - @margins.bottom, 0]) + else + d3.scale.ordinal() + .domain(Epoch.Util.domain(@getVisibleLayers())) + .rangeRoundBands([0, @innerHeight()], @options.padding.group, @options.outerPadding.group) + + # @return [Function] The x scale used to render the vertical bar chart. + y1: (y0) -> + d3.scale.ordinal() + .domain((layer.category for layer in @getVisibleLayers())) + .rangeRoundBands([0, y0.rangeBand()], @options.padding.bar, @options.outerPadding.bar) + + # Remaps the bar chart data into a form that is easier to display. + # @return [Array] The reorganized data. + _remapData: -> + map = {} + for layer in @getVisibleLayers() + className = 'bar ' + layer.className.replace(/\s*layer\s*/, '') + for entry in layer.values + map[entry.x] ?= [] + map[entry.x].push { label: layer.category, y: entry.y, className: className } + ({group: k, values: v} for own k, v of map) + + # Draws the bar char. + draw: -> + if @_isVertical() + @_drawVertical() + else + @_drawHorizontal() + super() + + # Draws the bar chart with a vertical orientation + _drawVertical: -> + [x0, y] = [@x(), @y()] + x1 = @x1(x0) + height = @height - @margins.top - @margins.bottom + data = @_remapData() + + # 1) Join + layer = @g.selectAll(".layer") + .data(data, (d) -> d.group) + + # 2) Update + layer.transition().duration(750) + .attr("transform", (d) -> "translate(#{x0(d.group)}, 0)") + + # 3) Enter / Create + layer.enter().append("g") + .attr('class', 'layer') + .attr("transform", (d) -> "translate(#{x0(d.group)}, 0)") + + rects = layer.selectAll('rect') + .data((group) -> group.values) + + rects.attr('class', (d) -> d.className) + + rects.transition().duration(600) + .attr('x', (d) -> x1(d.label)) + .attr('y', (d) -> y(d.y)) + .attr('width', x1.rangeBand()) + .attr('height', (d) -> height - y(d.y)) + + rects.enter().append('rect') + .attr('class', (d) -> d.className) + .attr('x', (d) -> x1(d.label)) + .attr('y', (d) -> y(d.y)) + .attr('width', x1.rangeBand()) + .attr('height', (d) -> height - y(d.y)) + + rects.exit().transition() + .duration(150) + .style('opacity', '0') + .remove() + + # 4) Update new and existing + + # 5) Exit / Remove + layer.exit() + .transition() + .duration(750) + .style('opacity', '0') + .remove() + + # Draws the bar chart with a horizontal orientation + _drawHorizontal: -> + [x, y0] = [@x(), @y()] + y1 = @y1(y0) + width = @width - @margins.left - @margins.right + data = @_remapData() + + # 1) Join + layer = @g.selectAll(".layer") + .data(data, (d) -> d.group) + + # 2) Update + layer.transition().duration(750) + .attr("transform", (d) -> "translate(0, #{y0(d.group)})") + + # 3) Enter / Create + layer.enter().append("g") + .attr('class', 'layer') + .attr("transform", (d) -> "translate(0, #{y0(d.group)})") + + rects = layer.selectAll('rect') + .data((group) -> group.values) + + rects.attr('class', (d) -> d.className) + + rects.transition().duration(600) + .attr('x', (d) -> 0) + .attr('y', (d) -> y1(d.label)) + .attr('height', y1.rangeBand()) + .attr('width', (d) -> x(d.y)) + + rects.enter().append('rect') + .attr('class', (d) -> d.className) + .attr('x', (d) -> 0) + .attr('y', (d) -> y1(d.label)) + .attr('height', y1.rangeBand()) + .attr('width', (d) -> x(d.y)) + + rects.exit().transition() + .duration(150) + .style('opacity', '0') + .remove() + + # 4) Update new and existing + + # 5) Exit / Remove + layer.exit() + .transition() + .duration(750) + .style('opacity', '0') + .remove() + + # Generates specific tick marks to emulate d3's linear scale axis ticks + # for ordinal scales. Note: this should only be called if the user has + # defined a set number of ticks for a given axis. + # @param [Number] numTicks Number of ticks to generate + # @param [String] dataKey Property name of a datum to use for the tick value + # @return [Array] The ticks for the given axis + _getTickValues: (numTicks, dataKey='x') -> + return [] unless @data[0]? + total = @data[0].values.length + step = Math.ceil(total / numTicks)|0 + tickValues = (@data[0].values[i].x for i in [0...total] by step) + + # @return [Function] d3 axis to use for the bottom of the visualization. + bottomAxis: -> + axis = d3.svg.axis().scale(@x()).orient('bottom') + .ticks(@options.ticks.bottom) + .tickFormat(@options.tickFormats.bottom) + if @_isVertical() and @options.ticks.bottom? + axis.tickValues @_getTickValues(@options.ticks.bottom) + axis + + # @return [Function] d3 axis to use for the top of the visualization. + topAxis: -> + axis = d3.svg.axis().scale(@x()).orient('top') + .ticks(@options.ticks.top) + .tickFormat(@options.tickFormats.top) + if @_isVertical() and @options.ticks.top? + axis.tickValues @_getTickValues(@options.ticks.top) + axis + + # @return [Function] d3 axis to use on the left of the visualization. + leftAxis: -> + axis = d3.svg.axis().scale(@y()).orient('left') + .ticks(@options.ticks.left) + .tickFormat(@options.tickFormats.left) + if @_isHorizontal() and @options.ticks.left? + axis.tickValues @_getTickValues(@options.ticks.left) + axis + + # @return [Function] d3 axis to use on the right of the visualization. + rightAxis: -> + axis = d3.svg.axis().scale(@y()).orient('right') + .ticks(@options.ticks.right) + .tickFormat(@options.tickFormats.right) + if @_isHorizontal() and @options.ticks.right? + axis.tickValues @_getTickValues(@options.ticks.right) + axis + + # Updates orientation in response <code>option:orientation</code>. + orientationChanged: -> + top = @options.tickFormats.top + bottom = @options.tickFormats.bottom + left = @options.tickFormats.left + right = @options.tickFormats.right + + @options.tickFormats.left = top + @options.tickFormats.right = bottom + @options.tickFormats.top = left + @options.tickFormats.bottom = right + + @draw() + + # Updates padding in response to <code>option:padding:*</code> and <code>option:outerPadding:*</code>. + paddingChanged: -> @draw() diff --git a/debian/missing-sources/epoch/src/basic/histogram.coffee b/debian/missing-sources/epoch/src/basic/histogram.coffee new file mode 100644 index 0000000..4548e67 --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/histogram.coffee @@ -0,0 +1,61 @@ +class Epoch.Chart.Histogram extends Epoch.Chart.Bar + defaults = + type: 'histogram' + domain: [0, 100] + bucketRange: [0, 100] + buckets: 10 + cutOutliers: false + + optionListeners = + 'option:bucketRange': 'bucketRangeChanged' + 'option:buckets': 'bucketsChanged' + 'option:cutOutliers': 'cutOutliersChanged' + + constructor: (@options={}) -> + super(@options = Epoch.Util.defaults(@options, defaults)) + @onAll optionListeners + @draw() + + # Prepares data by sorting it into histogram buckets as instructed by the chart options. + # @param [Array] data Data to prepare for rendering. + # @return [Array] The data prepared to be displayed as a histogram. + _prepareData: (data) -> + bucketSize = (@options.bucketRange[1] - @options.bucketRange[0]) / @options.buckets + + prepared = [] + for layer in data + buckets = (0 for i in [0...@options.buckets]) + for point in layer.values + index = parseInt((point.x - @options.bucketRange[0]) / bucketSize) + + if @options.cutOutliers and ((index < 0) or (index >= @options.buckets)) + continue + if index < 0 + index = 0 + else if index >= @options.buckets + index = @options.buckets - 1 + + buckets[index] += parseInt point.y + + preparedLayer = { values: (buckets.map (d, i) -> {x: parseInt(i) * bucketSize, y: d}) } + for own k, v of layer + preparedLayer[k] = v unless k == 'values' + + prepared.push preparedLayer + + return prepared + + # Called when options change, this prepares the raw data for the chart according to the new + # options, sets it, and renders the chart. + resetData: -> + @setData @rawData + @draw() + + # Updates the chart in response to an <code>option:bucketRange</code> event. + bucketRangeChanged: -> @resetData() + + # Updates the chart in response to an <code>option:buckets</code> event. + bucketsChanged: -> @resetData() + + # Updates the chart in response to an <code>option:cutOutliers</code> event. + cutOutliersChanged: -> @resetData() diff --git a/debian/missing-sources/epoch/src/basic/line.coffee b/debian/missing-sources/epoch/src/basic/line.coffee new file mode 100644 index 0000000..de56780 --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/line.coffee @@ -0,0 +1,46 @@ +# Static line chart implementation (using d3). +class Epoch.Chart.Line extends Epoch.Chart.Plot + constructor: (@options={}) -> + @options.type ?= 'line' + super(@options) + @draw() + + # @return [Function] The line generator used to construct the plot. + line: (layer) -> + [x, y] = [@x(), @y(layer.range)] + d3.svg.line() + .x((d) -> x(d.x)) + .y((d) -> y(d.y)) + + # Draws the line chart. + draw: -> + [x, y, layers] = [@x(), @y(), @getVisibleLayers()] + + # Zero visible layers, just drop all and get out + if layers.length == 0 + return @g.selectAll('.layer').remove() + + # 1) Join + layer = @g.selectAll('.layer') + .data(layers, (d) -> d.category) + + # 2) Update (only existing) + layer.select('.line').transition().duration(500) + .attr('d', (l) => @line(l)(l.values)) + + # 3) Enter (Create) + layer.enter().append('g') + .attr('class', (l) -> l.className) + .append('path') + .attr('class', 'line') + .attr('d', (l) => @line(l)(l.values)) + + # 4) Update (existing & new) + # Nuuupp + + # 5) Exit (Remove) + layer.exit().transition().duration(750) + .style('opacity', '0') + .remove() + + super() diff --git a/debian/missing-sources/epoch/src/basic/pie.coffee b/debian/missing-sources/epoch/src/basic/pie.coffee new file mode 100644 index 0000000..e794a2f --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/pie.coffee @@ -0,0 +1,59 @@ + +# Static Pie Chart implementation (using d3). +class Epoch.Chart.Pie extends Epoch.Chart.SVG + defaults = + type: 'pie' + margin: 10 + inner: 0 + + # Creates a new pie chart. + # @param [Object] options Options for the pie chart. + # @option options [Number] margin Margins to add around the pie chart (default: 10). + # @option options [Number] inner The inner radius for the chart (default: 0). + constructor: (@options={}) -> + super(@options = Epoch.Util.defaults(@options, defaults)) + @pie = d3.layout.pie().sort(null) + .value (d) -> d.value + @arc = d3.svg.arc() + .outerRadius(=> (Math.max(@width, @height) / 2) - @options.margin) + .innerRadius(=> @options.inner) + @g = @svg.append('g') + .attr("transform", "translate(#{@width/2}, #{@height/2})") + @on 'option:margin', 'marginChanged' + @on 'option:inner', 'innerChanged' + @draw() + + # Draws the pie chart + draw: -> + @g.selectAll('.arc').remove() + + arcs = @g.selectAll(".arc") + .data(@pie(@getVisibleLayers()), (d) -> d.data.category) + + arcs.enter().append('g') + .attr('class', (d) -> "arc pie " + d.data.className) + + arcs.select('path') + .attr('d', @arc) + + arcs.select('text') + .attr("transform", (d) => "translate(#{@arc.centroid(d)})") + .text((d) -> d.data.label or d.data.category) + + path = arcs.append("path") + .attr("d", @arc) + .each((d) -> @._current = d) + + text = arcs.append("text") + .attr("transform", (d) => "translate(#{@arc.centroid(d)})") + .attr("dy", ".35em") + .style("text-anchor", "middle") + .text((d) -> d.data.label or d.data.category) + + super() + + # Updates margins in response to an <code>option:margin</code> event. + marginChanged: -> @draw() + + # Updates inner margin in response to an <code>option:inner</code> event. + innerChanged: -> @draw() diff --git a/debian/missing-sources/epoch/src/basic/scatter.coffee b/debian/missing-sources/epoch/src/basic/scatter.coffee new file mode 100644 index 0000000..b8563ec --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/scatter.coffee @@ -0,0 +1,59 @@ + +# Static scatter plot implementation (using d3). +class Epoch.Chart.Scatter extends Epoch.Chart.Plot + defaults = + type: 'scatter' + radius: 3.5 + axes: ['top', 'bottom', 'left', 'right'] + + # Creates a new scatter plot. + # @param [Object] options Options for the plot. + # @option options [Number] radius The default radius to use for the points in + # the plot (default 3.5). This can be overrwitten by individual points. + constructor: (@options={}) -> + super(@options = Epoch.Util.defaults(@options, defaults)) + @on 'option:radius', 'radiusChanged' + @draw() + + # Draws the scatter plot. + draw: -> + [x, y, layers] = [@x(), @y(), @getVisibleLayers()] + radius = @options.radius + + if layers.length == 0 + return @g.selectAll('.layer').remove() + + layer = @g.selectAll('.layer') + .data(layers, (d) -> d.category) + + layer.enter().append('g') + .attr('class', (d) -> d.className) + + dots = layer.selectAll('.dot') + .data((l) -> l.values) + + dots.transition().duration(500) + .attr("r", (d) -> d.r ? radius) + .attr("cx", (d) -> x(d.x)) + .attr("cy", (d) -> y(d.y)) + + dots.enter().append('circle') + .attr('class', 'dot') + .attr("r", (d) -> d.r ? radius) + .attr("cx", (d) -> x(d.x)) + .attr("cy", (d) -> y(d.y)) + + dots.exit().transition() + .duration(750) + .style('opacity', 0) + .remove() + + layer.exit().transition() + .duration(750) + .style('opacity', 0) + .remove() + + super() + + # Updates radius in response to an <code>option:radius</code> event. + radiusChanged: -> @draw() diff --git a/debian/missing-sources/epoch/src/core/chart.coffee b/debian/missing-sources/epoch/src/core/chart.coffee new file mode 100644 index 0000000..068335a --- /dev/null +++ b/debian/missing-sources/epoch/src/core/chart.coffee @@ -0,0 +1,361 @@ +# The base class for all charts in Epoch. Defines chart dimensions, keeps a reference +# of the chart's containing elements. And defines core method for handling data and +# drawing. +class Epoch.Chart.Base extends Epoch.Events + defaults = + width: 320 + height: 240 + dataFormat: null + + optionListeners = + 'option:width': 'dimensionsChanged' + 'option:height': 'dimensionsChanged' + 'layer:shown': 'layerChanged' + 'layer:hidden': 'layerChanged' + + # Creates a new base chart. + # @param [Object] options Options to set for this chart. + # @option options [Integer] width Sets an explicit width for the visualization. + # @option options [Integer] height Sets an explicit height for the visualization. + # @option options [Object, String] dataFormat Specific data format for the chart. + # @option options [Object] model Data model for the chart. + constructor: (@options={}) -> + super() + + if @options.model + if @options.model.hasData()? + @setData(@options.model.getData(@options.type, @options.dataFormat)) + else + @setData(@options.data or []) + @options.model.on 'data:updated', => @setDataFromModel() + else + @setData(@options.data or []) + + if @options.el? + @el = d3.select(@options.el) + + @width = @options.width + @height = @options.height + + if @el? + @width = @el.width() unless @width? + @height = @el.height() unless @height? + else + @width = defaults.width unless @width? + @height = defaults.height unless @height? + @el = d3.select(document.createElement('DIV')) + .attr('width', @width) + .attr('height', @height) + + @onAll optionListeners + + # @return [Object] A copy of this charts options. + _getAllOptions: -> + Epoch.Util.defaults({}, @options) + + # Chart option accessor. + # @param key Name of the option to fetch. Can be hierarchical, e.g. 'margins.left' + # @return The requested option if found, undefined otherwise. + _getOption: (key) -> + parts = key.split('.') + scope = @options + while parts.length and scope? + subkey = parts.shift() + scope = scope[subkey] + scope + + # Chart option mutator. + # @param key Name of the option to fetch. Can be hierarchical, e.g. 'margins.top' + # @param value Value to set for the option. + # @event option:`key` Triggers an option event with the given key being set. + _setOption: (key, value) -> + parts = key.split('.') + scope = @options + while parts.length + subkey = parts.shift() + if parts.length == 0 + scope[subkey] = arguments[1] + @trigger "option:#{arguments[0]}" + return + unless scope[subkey]? + scope[subkey] = {} + scope = scope[subkey] + + # Sets all options given an object of mixed hierarchical keys and nested objects. + # @param [Object] options Options to set. + # @event option:* Triggers an option event for each key that was set + _setManyOptions: (options, prefix='') -> + for own key, value of options + if Epoch.isObject(value) + @_setManyOptions value, "#{prefix + key}." + else + @_setOption prefix + key, value + + # General accessor / mutator for chart options. + # + # @overload option() + # Fetches chart options. + # @return a copy of this chart's options. + # + # @overload option(name) + # Fetches the value the option with the given name. + # @param [String] name Name of the option to fetch. Can be hierarchical, e.g. <code>'margins.left'</code> + # @return The requested option if found, <code>undefined</code> otherwise. + # + # @overload option(name, value) + # Sets an option and triggers the associated event. + # @param [String] name Name of the option to fetch. Can be hierarchical, e.g. 'margins.top' + # @param value Value to set for the option. + # @event option:`name` Triggers an option event with the given key being set. + # + # @overload option(options) + # Sets multiple options at once. + # @param [Object] options Options to set for the chart. + # @event option:* Triggers an option event for each key that was set. + option: -> + if arguments.length == 0 + @_getAllOptions() + else if arguments.length == 1 and Epoch.isString(arguments[0]) + @_getOption arguments[0] + else if arguments.length == 2 and Epoch.isString(arguments[0]) + @_setOption arguments[0], arguments[1] + else if arguments.length == 1 and Epoch.isObject(arguments[0]) + @_setManyOptions arguments[0] + + # Retrieves and sets data from the chart's model + setDataFromModel: -> + prepared = @_prepareData @options.model.getData(@options.type, @options.dataFormat) + @data = @_annotateLayers(prepared) + @draw() + + # Set the initial data for the chart. + # @param data Data to initially set for the given chart. The data format can vary + # from chart to chart. The base class assumes that the data provided will be an + # array of layers. + setData: (data, options={}) -> + prepared = @_prepareData (@rawData = @_formatData(data)) + @data = @_annotateLayers(prepared) + + # Performs post formatted data preparation. + # @param data Data to prepare before setting. + # @return The prepared data. + _prepareData: (data) -> data + + # Performs data formatting before setting the charts data + # @param data Data to be formatted. + # @return The chart specific formatted data. + _formatData: (data) -> + Epoch.Data.formatData(data, @options.type, @options.dataFormat) + + # Annotates data to add class names, categories, and initial visibility states + _annotateLayers: (data) -> + category = 1 + for layer in data + classes = ['layer'] + classes.push "category#{category}" + layer.category = category + layer.visible = true + classes.push(Epoch.Util.dasherize layer.label) if layer.label? + layer.className = classes.join(' ') + category++ + return data + + # Finds a layer in the chart's current data that has the given label or index. + # @param [String, Number] labelOrIndex The label or index of the layer to find. + _findLayer: (labelOrIndex) -> + layer = null + if Epoch.isString(labelOrIndex) + for l in @data + if l.label == labelOrIndex + layer = l + break + else if Epoch.isNumber(labelOrIndex) + index = parseInt(labelOrIndex) + layer = @data[index] unless index < 0 or index >= @data.length + return layer + + # Instructs the chart that a data layer should be displayed. + # @param [String, Number] labelOrIndex The label or index of the layer to show. + # @event 'layer:shown' If a layer that was previously hidden now became visible. + showLayer: (labelOrIndex) -> + return unless (layer = @_findLayer labelOrIndex) + return if layer.visible + layer.visible = true + @trigger 'layer:shown' + + # Instructs the chart that a data layer should not be displayed. + # @param [String, Number] labelOrIndex The label or index of the layer to hide. + # @event 'layer:hidden' If a layer that was visible was made hidden. + hideLayer: (labelOrIndex) -> + return unless (layer = @_findLayer labelOrIndex) + return unless layer.visible + layer.visible = false + @trigger 'layer:hidden' + + # Instructs the chart that a data layer's visibility should be toggled. + # @param [String, Number] labelOrIndex The label or index of the layer to toggle. + # @event 'layer:shown' If the layer was made visible + # @event 'layer:hidden' If the layer was made invisible + toggleLayer: (labelOrIndex) -> + return unless (layer = @_findLayer labelOrIndex) + layer.visible = !layer.visible + if layer.visible + @trigger 'layer:shown' + else + @trigger 'layer:hidden' + + # Determines whether or not a data layer is visible. + # @param [String, Number] labelOrIndex The label or index of the layer to toggle. + # @return <code>true</code> if the layer is visible, <code>false</code> otherwise. + isLayerVisible: (labelOrIndex) -> + return null unless (layer = @_findLayer labelOrIndex) + layer.visible + + # Calculates an array of layers in the charts data that are flagged as visible. + # @return [Array] The chart's visible layers. + getVisibleLayers: -> + return @data.filter((layer) -> layer.visible) + + # Updates the chart with new data. + # @param data Data to replace the current data for the chart. + # @param [Boolean] draw Whether or not to redraw the chart after the data has been set. + # Default: true. + update: (data, draw=true) -> + @setData data + @draw() if draw + + # Draws the chart. Triggers the 'draw' event, subclasses should call super() after drawing to + # ensure that the event is triggered. + # @abstract Must be overriden in child classes to perform chart specific drawing. + draw: -> @trigger 'draw' + + # Determines a resulting scale domain for the y axis given a domain + # @param [Mixed] givenDomain A set domain, a label associated with mutliple + # layers, or null. + # @return The domain for the y scale + _getScaleDomain: (givenDomain) -> + # Explicitly set given domain + if Array.isArray(givenDomain) + return givenDomain + + # Check for "labeled" layer ranges + if Epoch.isString(givenDomain) + layers = @getVisibleLayers() + .filter((l) -> l.range == givenDomain) + .map((l) -> l.values) + if layers? && layers.length + values = Epoch.Util.flatten(layers).map((d) -> d.y) + minFn = (memo, curr) -> if curr < memo then curr else memo + maxFn = (memo, curr) -> if curr > memo then curr else memo + return [values.reduce(minFn, values[0]), values.reduce(maxFn, values[0])] + + # Find the domain based on chart options + if Array.isArray(@options.range) + @options.range + else if @options.range && Array.isArray(@options.range.left) + @options.range.left + else if @options.range && Array.isArray(@options.range.right) + @options.range.right + else + @extent((d) -> d.y) + + # Calculates an extent throughout the layers based on the given comparator. + # @param [Function] cmp Comparator to use for performing the min and max for the extent + # calculation. + # @return [Array] an extent array with the first element as the minimum value in the + # chart's data set and the second element as the maximum. + extent: (cmp) -> + [ + d3.min(@getVisibleLayers(), (layer) -> d3.min(layer.values, cmp)), + d3.max(@getVisibleLayers(), (layer) -> d3.max(layer.values, cmp)) + ] + + # Updates the width and height members and container dimensions in response to an + # 'option:width' or 'option:height' event. + dimensionsChanged: -> + @width = @option('width') or @width + @height = @option('height') or @height + @el.width(@width) + @el.height(@height) + + # Updates the chart in response to a layer being shown or hidden + layerChanged: -> + @draw() + +# Base class for all SVG charts (via d3). +class Epoch.Chart.SVG extends Epoch.Chart.Base + # Initializes the chart and places the rendering SVG in the specified HTML + # containing element. + # @param [Object] options Options for the SVG chart. + # @option options [HTMLElement] el Container element for the chart. + # @option options [Array] data Layered data used to render the chart. + constructor: (@options={}) -> + super(@options) + if @el? + @svg = @el.append('svg') + else + @svg = d3.select(document.createElement('svg')) + @svg.attr + xmlns: 'http://www.w3.org/2000/svg', + width: @width, + height: @height + + # Resizes the svg element in response to a 'option:width' or 'option:height' event. + dimensionsChanged: -> + super() + @svg.attr('width', @width).attr('height', @height) + +# Base Class for all Canvas based charts. +class Epoch.Chart.Canvas extends Epoch.Chart.Base + # Initializes the chart and places the rendering canvas in the specified + # HTML container element. + # @param [Object] options Options for the SVG chart. + # @option options [HTMLElement] el Container element for the chart. + # @option options [Array] data Layered data used to render the chart. + constructor: (@options={}) -> + super(@options) + + if @options.pixelRatio? + @pixelRatio = @options.pixelRatio + else if window.devicePixelRatio? + @pixelRatio = window.devicePixelRatio + else + @pixelRatio = 1 + + @canvas = d3.select( document.createElement('CANVAS') ) + @canvas.style + 'width': "#{@width}px" + 'height': "#{@height}px" + + @canvas.attr + width: @getWidth() + height: @getHeight() + + @el.node().appendChild @canvas.node() if @el? + @ctx = Epoch.Util.getContext @canvas.node() + + # @return [Number] width of the canvas with respect to the pixel ratio of the display + getWidth: -> @width * @pixelRatio + + # @return [Number] height of the canvas with respect to the pixel ratio of the display + getHeight: -> @height * @pixelRatio + + # Clears the render canvas. + clear: -> + @ctx.clearRect(0, 0, @getWidth(), @getHeight()) + + # @return [Object] computed styles for the given selector in the context of this chart. + # @param [String] selector The selector used to compute the styles. + getStyles: (selector) -> + Epoch.QueryCSS.getStyles(selector, @el) + + # Resizes the canvas element when the dimensions of the container change + dimensionsChanged: -> + super() + @canvas.style {'width': "#{@width}px", 'height': "#{@height}px"} + @canvas.attr { width: @getWidth(), height: @getHeight() } + + # Purges QueryCSS cache and redraws the Canvas based chart. + redraw: -> + Epoch.QueryCSS.purge() + @draw() diff --git a/debian/missing-sources/epoch/src/core/context.coffee b/debian/missing-sources/epoch/src/core/context.coffee new file mode 100644 index 0000000..8566552 --- /dev/null +++ b/debian/missing-sources/epoch/src/core/context.coffee @@ -0,0 +1,25 @@ +# Rendering context used for unit testing. +class Epoch.TestContext + VOID_METHODS = [ + 'arc', 'arcTo', 'beginPath', 'bezierCurveTo', 'clearRect', + 'clip', 'closePath', 'drawImage', 'fill', 'fillRect', 'fillText', + 'moveTo', 'quadraticCurveTo', 'rect', 'restore', 'rotate', 'save', + 'scale', 'scrollPathIntoView', 'setLineDash', 'setTransform', + 'stroke', 'strokeRect', 'strokeText', 'transform', 'translate', 'lineTo' + ] + + # Creates a new test rendering context. + constructor: -> + @_log = [] + @_makeFauxMethod(method) for method in VOID_METHODS + + # Creates a fake method with the given name that logs the method called + # and arguments passed when executed. + # @param name Name of the fake method to create. + _makeFauxMethod: (name) -> + @[name] = -> @_log.push "#{name}(#{(arg.toString() for arg in arguments).join(',')})" + + # Faux method that emulates the "getImageData" method + getImageData: -> + @_log.push "getImageData(#{(arg.toString() for arg in arguments).join(',')})" + return { width: 0, height: 0, resolution: 1.0, data: [] } diff --git a/debian/missing-sources/epoch/src/core/css.coffee b/debian/missing-sources/epoch/src/core/css.coffee new file mode 100644 index 0000000..03a7310 --- /dev/null +++ b/debian/missing-sources/epoch/src/core/css.coffee @@ -0,0 +1,131 @@ +# Singelton class used to query CSS styles by way of reference elements. +# This allows canvas based visualizations to use the same styles as their +# SVG counterparts. +class QueryCSS + # Reference container id + REFERENCE_CONTAINER_ID = '_canvas_css_reference' + + # Container Hash Attribute + CONTAINER_HASH_ATTR = 'data-epoch-container-id' + + # Handles automatic container id generation + containerCount = 0 + nextContainerId = -> "epoch-container-#{containerCount++}" + + # Expression used to derive tag name, id, and class names from + # selectors given the the put method. + PUT_EXPR = /^([^#. ]+)?(#[^. ]+)?(\.[^# ]+)?$/ + + # Whether or not to log full selector lists + logging = false + + # Converts selectors into actual dom elements (replaces put.js) + # Limited the functionality to what Epoch actually needs to + # operate correctly. We detect class names, ids, and element + # tag names. + put = (selector) -> + match = selector.match(PUT_EXPR) + return Epoch.error('Query CSS cannot match given selector: ' + selector) unless match? + [whole, tag, id, classNames] = match + tag = (tag ? 'div').toUpperCase() + + element = document.createElement(tag) + element.id = id.substr(1) if id? + if classNames? + element.className = classNames.substr(1).replace(/\./g, ' ') + + return element + + # Lets the user set whether or not to log selector lists and resulting DOM trees. + # Useful for debugging QueryCSS itself. + @log: (b) -> + logging = b + + # Key-Value cache for computed styles that we found using this class. + @cache = {} + + # List of styles to pull from the full list of computed styles + @styleList = ['fill', 'stroke', 'stroke-width'] + + # The svg reference container + @container = null + + # Purges the selector to style cache + @purge: -> + QueryCSS.cache = {} + + # Gets the reference element container. + @getContainer: -> + return QueryCSS.container if QueryCSS.container? + container = document.createElement('DIV') + container.id = REFERENCE_CONTAINER_ID + document.body.appendChild(container) + QueryCSS.container = d3.select(container) + + # @return [String] A unique identifier for the given container and selector. + # @param [String] selector Selector from which to derive the styles + # @param container The containing element for a chart. + @hash: (selector, container) -> + containerId = container.attr(CONTAINER_HASH_ATTR) + unless containerId? + containerId = nextContainerId() + container.attr(CONTAINER_HASH_ATTR, containerId) + return "#{containerId}__#{selector}" + + # @return The computed styles for the given selector in the given container element. + # @param [String] selector Selector from which to derive the styles. + # @param container HTML containing element in which to place the reference SVG. + @getStyles: (selector, container) -> + # 0) Check for cached styles + cacheKey = QueryCSS.hash(selector, container) + cache = QueryCSS.cache[cacheKey] + return cache if cache? + + # 1) Build a full reference tree (parents, container, and selector elements) + parents = [] + parentNode = container.node().parentNode + + while parentNode? and parentNode.nodeName.toLowerCase() != 'body' + parents.unshift parentNode + parentNode = parentNode.parentNode + parents.push container.node() + + selectorList = [] + for element in parents + sel = element.nodeName.toLowerCase() + if element.id? and element.id.length > 0 + sel += '#' + element.id + if element.className? and element.className.length > 0 + sel += '.' + Epoch.Util.trim(element.className).replace(/\s+/g, '.') + selectorList.push sel + + selectorList.push('svg') + + for subSelector in Epoch.Util.trim(selector).split(/\s+/) + selectorList.push(subSelector) + + console.log(selectorList) if logging + + parent = root = put(selectorList.shift()) + while selectorList.length + el = put(selectorList.shift()) + parent.appendChild el + parent = el + + console.log(root) if logging + + # 2) Place the reference tree and fetch styles given the selector + QueryCSS.getContainer().node().appendChild(root) + + ref = d3.select('#' + REFERENCE_CONTAINER_ID + ' ' + selector) + styles = {} + for name in QueryCSS.styleList + styles[name] = ref.style(name) + QueryCSS.cache[cacheKey] = styles + + # 3) Cleanup and return the styles + QueryCSS.getContainer().html('') + return styles + + +Epoch.QueryCSS = QueryCSS
\ No newline at end of file diff --git a/debian/missing-sources/epoch/src/core/d3.coffee b/debian/missing-sources/epoch/src/core/d3.coffee new file mode 100644 index 0000000..a33d717 --- /dev/null +++ b/debian/missing-sources/epoch/src/core/d3.coffee @@ -0,0 +1,25 @@ +# Gets the width of the first node, or sets the width of all the nodes +# in a d3 selection. +# @param value [Number, String] (optional) Width to set for all the nodes in the selection. +# @return The selection if setting the width of the nodes, or the width +# in pixels of the first node in the selection. +d3.selection::width = (value) -> + if value? and Epoch.isString(value) + @style('width', value) + else if value? and Epoch.isNumber(value) + @style('width', "#{value}px") + else + +Epoch.Util.getComputedStyle(@node(), null).width.replace('px', '') + +# Gets the height of the first node, or sets the height of all the nodes +# in a d3 selection. +# @param value (optional) Height to set for all the nodes in the selection. +# @return The selection if setting the height of the nodes, or the height +# in pixels of the first node in the selection. +d3.selection::height = (value) -> + if value? and Epoch.isString(value) + @style('height', value) + else if value? and Epoch.isNumber(value) + @style('height', "#{value}px") + else + +Epoch.Util.getComputedStyle(@node(), null).height.replace('px', '')
\ No newline at end of file diff --git a/debian/missing-sources/epoch/src/core/format.coffee b/debian/missing-sources/epoch/src/core/format.coffee new file mode 100644 index 0000000..d65932f --- /dev/null +++ b/debian/missing-sources/epoch/src/core/format.coffee @@ -0,0 +1,15 @@ +# Tick formatter identity. +Epoch.Formats.regular = (d) -> d + +# Tick formatter that formats the numbers using standard SI postfixes. +Epoch.Formats.si = (d) -> Epoch.Util.formatSI(d) + +# Tick formatter for percentages. +Epoch.Formats.percent = (d) -> (d*100).toFixed(1) + "%" + +# Tick formatter for seconds from timestamp data. +Epoch.Formats.seconds = (t) -> d3Seconds(new Date(t*1000)) +d3Seconds = d3.time.format('%I:%M:%S %p') + +# Tick formatter for bytes +Epoch.Formats.bytes = (d) -> Epoch.Util.formatBytes(d) diff --git a/debian/missing-sources/epoch/src/core/util.coffee b/debian/missing-sources/epoch/src/core/util.coffee new file mode 100644 index 0000000..30995bf --- /dev/null +++ b/debian/missing-sources/epoch/src/core/util.coffee @@ -0,0 +1,236 @@ +typeFunction = (objectName) -> (v) -> + Object::toString.call(v) == "[object #{objectName}]" + +# @return [Boolean] <code>true</code> if the given value is an array, <code>false</code> otherwise. +# @param v Value to test. +Epoch.isArray = Array.isArray ? typeFunction('Array') + +# @return [Boolean] <code>true</code> if the given value is an object, <code>false</code> otherwise. +# @param v Value to test. +Epoch.isObject = typeFunction('Object') + +# @return [Boolean] <code>true</code> if the given value is a string, <code>false</code> otherwise. +# @param v Value to test. +Epoch.isString = typeFunction('String') + +# @return [Boolean] <code>true</code> if the given value is a function, <code>false</code> otherwise. +# @param v Value to test. +Epoch.isFunction = typeFunction('Function') + +# @return [Boolean] <code>true</code> if the given value is a number, <code>false</code> otherwise. +# @param v Value to test. +Epoch.isNumber = typeFunction('Number') + +# Attempts to determine if a given value represents a DOM element. The result is always correct if the +# browser implements DOM Level 2, but one can fool it on certain versions of IE. Adapted from: +# <a href="http://goo.gl/yaD9hV">Stack Overflow #384286</a>. +# @return [Boolean] <code>true</code> if the given value is a DOM element, <code>false</code> otherwise. +# @param v Value to test. +Epoch.isElement = (v) -> + if HTMLElement? + v instanceof HTMLElement + else + v? and Epoch.isObject(v) and v.nodeType == 1 and Epoch.isString(v.nodeName) + +# Determines if a given value is a non-empty array. +# @param v Value to test. +# @return [Boolean] <code>true</code> if the given value is an array with at least one element. +Epoch.isNonEmptyArray = (v) -> + Epoch.isArray(v) and v.length > 0 + +# Generates shallow copy of an object. +# @return A shallow copy of the given object. +# @param [Object] original Object for which to make the shallow copy. +Epoch.Util.copy = (original) -> + return null unless original? + copy = {} + copy[k] = v for own k, v of original + return copy + +# Creates a deep copy of the given options filling in missing defaults. +# @param [Object] options Options to copy. +# @param [Object] defaults Default values for the options. +Epoch.Util.defaults = (options, defaults) -> + result = Epoch.Util.copy(options) + for own k, v of defaults + opt = options[k] + def = defaults[k] + bothAreObjects = Epoch.isObject(opt) and Epoch.isObject(def) + + if opt? and def? + if bothAreObjects and not Epoch.isArray(opt) + result[k] = Epoch.Util.defaults(opt, def) + else + result[k] = opt + else if opt? + result[k] = opt + else + result[k] = def + + return result + +# Formats numbers with standard postfixes (e.g. K, M, G) +# @param [Number] v Value to format. +# @param [Integer] fixed Number of floating point digits to fix after conversion. +# @param [Boolean] fixIntegers Whether or not to add floating point digits to non-floating point results. +# @example Formatting a very large number +# Epoch.Util.formatSI(1120000) == "1.1 M" +Epoch.Util.formatSI = (v, fixed=1, fixIntegers=false) -> + if v < 1000 + q = v + q = q.toFixed(fixed) unless (q|0) == q and !fixIntegers + return q + + for own i, label of ['K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] + base = Math.pow(10, ((i|0)+1)*3) + if v >= base and v < Math.pow(10, ((i|0)+2)*3) + q = v/base + q = q.toFixed(fixed) unless (q % 1) == 0 and !fixIntegers + return "#{q} #{label}" + +# Formats large bandwidth and disk space usage numbers with byte postfixes (e.g. KB, MB, GB, etc.) +# @param [Number] v Value to format. +# @param [Integer] fixed Number of floating point digits to fix after conversion. +# @param [Boolean] fixIntegers Whether or not to add floating point digits to non-floating point results. +# @example Formatting a large number of bytes +# Epoch.Util.formatBytes(5.21 * Math.pow(2, 20)) == "5.2 MB" +Epoch.Util.formatBytes = (v, fixed=1, fix_integers=false) -> + if v < 1024 + q = v + q = q.toFixed(fixed) unless (q % 1) == 0 and !fix_integers + return "#{q} B" + + for own i, label of ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + base = Math.pow(1024, (i|0)+1) + if v >= base and v < Math.pow(1024, (i|0)+2) + q = v/base + q = q.toFixed(fixed) unless (q % 1) == 0 and !fix_integers + return "#{q} #{label}" + +# @return a "dasherized" css class names from a given string +# @example Using dasherize +# Epoch.Util.dasherize('My Awesome Name') == 'my-awesome-name' +Epoch.Util.dasherize = (str) -> + Epoch.Util.trim(str).replace("\n", '').replace(/\s+/g, '-').toLowerCase() + +# @return the full domain of a given variable from an array of layers +# @param [Array] layers Layered plot data. +# @param [String] key The key name of the value at on each entry in the layers. +Epoch.Util.domain = (layers, key='x') -> + set = {} + domain = [] + for layer in layers + for entry in layer.values + continue if set[entry[key]]? + domain.push(entry[key]) + set[entry[key]] = true + return domain + +# Strips whitespace from the beginning and end of a string. +# @param [String] string String to trim. +# @return [String] The string without leading or trailing whitespace. +# Returns null if the given parameter was not a string. +Epoch.Util.trim = (string) -> + return null unless Epoch.isString(string) + string.replace(/^\s+/g, '').replace(/\s+$/g, '') + +# Returns the computed styles of an element in the document +# @param [HTMLElement] Element for which to fetch the styles. +# @param [String] pseudoElement Pseudo selectors on which to search for the element. +# @return [Object] The styles for the given element. +Epoch.Util.getComputedStyle = (element, pseudoElement) -> + if Epoch.isFunction(window.getComputedStyle) + window.getComputedStyle(element, pseudoElement) + else if element.currentStyle? + element.currentStyle + +# Converts a CSS color string into an RGBA string with the given opacity +# @param [String] color Color string to convert into an rgba +# @param [Number] opacity Opacity to use for the resulting color. +# @return the resulting rgba color string. +Epoch.Util.toRGBA = (color, opacity) -> + if (parts = color.match /^rgba\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*[0-9\.]+\)/) + [all, r, g, b] = parts + result = "rgba(#{r},#{g},#{b},#{opacity})" + else if (v = d3.rgb color) + result = "rgba(#{v.r},#{v.g},#{v.b},#{opacity})" + return result + +# Obtains a graphics context for the given canvas node. Nice to have +# this abstracted out in case we want to support WebGL in the future. +# Also allows us to setup a special context when unit testing, as +# jsdom doesn't have canvas support, and node-canvas is a pain in the +# butt to install properly across different platforms. +Epoch.Util.getContext = (node, type='2d') -> + node.getContext(type) + +# Basic eventing base class for all Epoch classes. +class Epoch.Events + constructor: -> + @_events = {} + + # Registers a callback to a given event. + # @param [String] name Name of the event. + # @param [Function, String] callback Either a closure to call when the event fires + # or a string that denotes a method name to call on this object. + on: (name, callback) -> + return unless callback? + @_events[name] ?= [] + @_events[name].push callback + + # Registers a map of event names to given callbacks. This method calls <code>.on</code> + # directly for each of the events given. + # @param [Object] map A map of event names to callbacks. + onAll: (map) -> + return unless Epoch.isObject(map) + @on(name, callback) for own name, callback of map + + # Removes a specific callback listener or all listeners for a given event. + # @param [String] name Name of the event. + # @param [Function, String] callback (Optional) Callback to remove from the listener list. + # If this parameter is not provided then all listeners will be removed for the event. + off: (name, callback) -> + return unless Epoch.isArray(@_events[name]) + return delete(@_events[name]) unless callback? + while (i = @_events[name].indexOf(callback)) >= 0 + @_events[name].splice(i, 1) + + # Removes a set of callback listeners for all events given in the map or array of strings. + # This method calls <code>.off</code> directly for each event and callback to remove. + # @param [Object, Array] mapOrList Either a map that associates event names to specific callbacks + # or an array of event names for which to completely remove listeners. + offAll: (mapOrList) -> + if Epoch.isArray(mapOrList) + @off(name) for name in mapOrList + else if Epoch.isObject(mapOrList) + @off(name, callback) for own name, callback of mapOrList + + # Triggers an event causing all active listeners to be executed. + # @param [String] name Name of the event to fire. + trigger: (name) -> + return unless @_events[name]? + args = (arguments[i] for i in [1...arguments.length]) + for callback in @_events[name] + fn = null + if Epoch.isString(callback) + fn = @[callback] + else if Epoch.isFunction(callback) + fn = callback + unless fn? + Epoch.exception "Callback for event '#{name}' is not a function or reference to a method." + fn.apply @, args + +# Performs a single pass flatten on a multi-array +# @param [Array] multiarray A deep multi-array to flatten +# @returns [Array] A single pass flatten of the multi-array +Epoch.Util.flatten = (multiarray) -> + if !Array.isArray(multiarray) + throw new Error('Epoch.Util.flatten only accepts arrays') + result = [] + for array in multiarray + if Array.isArray(array) + for item in array + result.push item + else + result.push array + result diff --git a/debian/missing-sources/epoch/src/data.coffee b/debian/missing-sources/epoch/src/data.coffee new file mode 100644 index 0000000..7627fd4 --- /dev/null +++ b/debian/missing-sources/epoch/src/data.coffee @@ -0,0 +1,311 @@ +Epoch.Data ?= {} +Epoch.Data.Format ?= {} + +# Private Helper Function for data formats below +applyLayerLabel = (layer, options, i, keys=[]) -> + [labels, autoLabels, keyLabels] = [options.labels, options.autoLabels, options.keyLabels] + if labels? and Epoch.isArray(labels) and labels.length > i + layer.label = labels[i] + else if keyLabels and keys.length > i + layer.label = keys[i] + else if autoLabels + label = [] + while i >= 0 + label.push String.fromCharCode(65+(i%26)) + i -= 26 + layer.label = label.join('') + return layer + +# Formats a given input array for the chart of the specified type. Notes: +# +# * Basic pie charts require a flat array of numbers +# * Real-time histogram charts require sparse histogram objects +# +# @param data Data array to format (can be multidimensional to allow for multiple layers). +# @option options [String] type Type of chart for which to format the data. +# @option options [Function] x(d, i) Maps the data to x values given a data point and the index of the point. +# @option options [Function] y(d, i) Maps the data to y values given a data point and the index of the point. +# @option options [Function] time(d, i, startTime) Maps the data to time values for real-time plots given the point and index. +# @option options [Array] labels Labels to apply to each data layer. +# @option options [Boolean] autoLabels Apply labels of ascending capital letters to each layer if true. +# @option options [Number] startTime Unix timestamp used as the starting point for auto acsending times in +# real-time data formatting. +Epoch.Data.Format.array = (-> + defaultOptions = + x: (d, i) -> i + y: (d, i) -> d + time: (d, i, startTime) -> parseInt(startTime) + parseInt(i) + type: 'area' + autoLabels: false + labels: [] + startTime: parseInt(new Date().getTime() / 1000) + + buildLayers = (data, options, mapFn) -> + result = [] + if Epoch.isArray(data[0]) + for own i, series of data + result.push applyLayerLabel({values: series.map(mapFn)}, options, parseInt(i)) + else + result.push applyLayerLabel({values: data.map(mapFn)}, options, 0) + return result + + formatBasicPlot = (data, options) -> + buildLayers data, options, (d, i) -> + { x: options.x(d, i), y: options.y(d, i) } + + formatTimePlot = (data, options) -> + buildLayers data, options, (d, i) -> + { time: options.time(d, i, options.startTime), y: options.y(d, i) } + + formatHeatmap = (data, options) -> + buildLayers data, options, (d, i) -> + { time: options.time(d, i, options.startTime), histogram: d } + + formatPie = (data, options) -> + result = [] + for own i, v of data + return [] unless Epoch.isNumber(data[0]) + result.push applyLayerLabel({ value: v }, options, i) + return result + + format = (data=[], options={}) -> + return [] unless Epoch.isNonEmptyArray(data) + opt = Epoch.Util.defaults options, defaultOptions + + if opt.type == 'time.heatmap' + formatHeatmap data, opt + else if opt.type.match /^time\./ + formatTimePlot data, opt + else if opt.type == 'pie' + formatPie data, opt + else + formatBasicPlot data, opt + + format.entry = (datum, options={}) -> + if options.type == 'time.gauge' + return 0 unless datum? + opt = Epoch.Util.defaults options, defaultOptions + d = if Epoch.isArray(datum) then datum[0] else datum + return opt.y(d, 0) + + return [] unless datum? + unless options.startTime? + options.startTime = parseInt(new Date().getTime() / 1000) + + if Epoch.isArray(datum) + data = datum.map (d) -> [d] + else + data = [datum] + + (layer.values[0] for layer in format(data, options)) + + return format +)() + +# Formats an input array of tuples such that the first element of the tuple is set +# as the x-coordinate and the second element as the y-coordinate. Supports layers +# of tupled series. For real-time plots the first element of a tuple is set as the +# time component of the value. +# +# This formatter will return an empty array if the chart <code>type</code> option is +# set as 'time.heatmap', 'time.gauge', or 'pie'. +# +# @param data Data array to format (can be multidimensional to allow for multiple layers). +# @option options [String] type Type of chart for which to format the data. +# @option options [Function] x(d, i) Maps the data to x values given a data point and the index of the point. +# @option options [Function] y(d, i) Maps the data to y values given a data point and the index of the point. +# @option options [Function] time(d, i, startTime) Maps the data to time values for real-time plots given the point and index. +# @option options [Array] labels Labels to apply to each data layer. +# @option options [Boolean] autoLabels Apply labels of ascending capital letters to each layer if true. +Epoch.Data.Format.tuple = (-> + defaultOptions = + x: (d, i) -> d + y: (d, i) -> d + time: (d, i) -> d + type: 'area' + autoLabels: false + labels: [] + + buildLayers = (data, options, mapFn) -> + return [] unless Epoch.isArray(data[0]) + result = [] + if Epoch.isArray(data[0][0]) + for own i, series of data + result.push applyLayerLabel({values: series.map(mapFn)}, options, parseInt(i)) + else + result.push applyLayerLabel({values: data.map(mapFn)}, options, 0) + return result + + format = (data=[], options={}) -> + return [] unless Epoch.isNonEmptyArray(data) + opt = Epoch.Util.defaults options, defaultOptions + + if opt.type == 'pie' or opt.type == 'time.heatmap' or opt.type == 'time.gauge' + return [] + else if opt.type.match /^time\./ + buildLayers data, opt, (d, i) -> + {time: opt.time(d[0], parseInt(i)), y: opt.y(d[1], parseInt(i))} + else + buildLayers data, opt, (d, i) -> + {x: opt.x(d[0], parseInt(i)), y: opt.y(d[1], parseInt(i))} + + format.entry = (datum, options={}) -> + return [] unless datum? + unless options.startTime? + options.startTime = parseInt(new Date().getTime() / 1000) + + if Epoch.isArray(datum) and Epoch.isArray(datum[0]) + data = datum.map (d) -> [d] + else + data = [datum] + + (layer.values[0] for layer in format(data, options)) + + return format +)() + +# This formatter expects to be passed a flat array of objects and a list of keys. +# It then extracts the value for each key across each of the objects in the array +# to produce multi-layer plot data of the given chart type. Note that this formatter +# also can be passed an <code>x</code> or <code>time</code> option as a string that +# allows the programmer specify a key to use for the value of the first component +# (x or time) of each resulting layer value. +# +# Note that this format does not work with basic pie charts nor real-time gauge charts. +# +# @param [Array] data Flat array of objects to format. +# @param [Array] keys List of keys used to extract data from each of the objects. +# @option options [String] type Type of chart for which to format the data. +# @option options [Function, String] x Either the key to use for the x-componet of +# the resulting values or a function of the data at that point and index of the data. +# @option options [Function, String] time Either an object key or function to use for the +# time-component of resulting real-time plot values. +# @option options [Function] y(d, i) Maps the data to y values given a data point and the index of the point. +# @option options [Array] labels Labels to apply to each data layer. +# @option options [Boolean] autoLabels Apply labels of ascending capital letters to each layer if true. +# @option options [Boolean] keyLabels Apply labels using the keys passed to the formatter (defaults to true). +# @option options [Number] startTime Unix timestamp used as the starting point for auto acsending times in +# real-time data formatting. +Epoch.Data.Format.keyvalue = (-> + defaultOptions = + type: 'area', + x: (d, i) -> parseInt(i) + y: (d, i) -> d + time: (d, i, startTime) -> parseInt(startTime) + parseInt(i) + labels: [] + autoLabels: false + keyLabels: true + startTime: parseInt(new Date().getTime() / 1000) + + buildLayers = (data, keys, options, mapFn) -> + result = [] + for own j, key of keys + values = [] + for own i, d of data + values.push mapFn(d, key, parseInt(i)) + result.push applyLayerLabel({ values: values }, options, parseInt(j), keys) + return result + + formatBasicPlot = (data, keys, options) -> + buildLayers data, keys, options, (d, key, i) -> + if Epoch.isString(options.x) + x = d[options.x] + else + x = options.x(d, parseInt(i)) + { x: x, y: options.y(d[key], parseInt(i)) } + + formatTimePlot = (data, keys, options, rangeName='y') -> + buildLayers data, keys, options, (d, key, i) -> + if Epoch.isString(options.time) + value = { time: d[options.time] } + else + value = { time: options.time(d, parseInt(i), options.startTime) } + value[rangeName] = options.y(d[key], parseInt(i)) + value + + format = (data=[], keys=[], options={}) -> + return [] unless Epoch.isNonEmptyArray(data) and Epoch.isNonEmptyArray(keys) + opt = Epoch.Util.defaults options, defaultOptions + + if opt.type == 'pie' or opt.type == 'time.gauge' + return [] + else if opt.type == 'time.heatmap' + formatTimePlot data, keys, opt, 'histogram' + else if opt.type.match /^time\./ + formatTimePlot data, keys, opt + else + formatBasicPlot data, keys, opt + + format.entry = (datum, keys=[], options={}) -> + return [] unless datum? and Epoch.isNonEmptyArray(keys) + unless options.startTime? + options.startTime = parseInt(new Date().getTime() / 1000) + (layer.values[0] for layer in format([datum], keys, options)) + + return format +)() + +# Convenience data formatting method for easily accessing the various formatters. +# @param [String] formatter Name of the formatter to use. +# @param [Array] data Data to format. +# @param [Object] options Options to pass to the formatter (if any). +Epoch.data = (formatter, args...) -> + return [] unless (formatFn = Epoch.Data.Format[formatter])? + formatFn.apply formatFn, args + + +# Method used by charts and models for handling option based data formatting. +# Abstracted here because we'd like to allow models and indivisual charts to +# perform this action depending on the context. +Epoch.Data.formatData = (data=[], type, dataFormat) -> + return data unless Epoch.isNonEmptyArray(data) + + if Epoch.isString(dataFormat) + opts = { type: type } + return Epoch.data(dataFormat, data, opts) + + return data unless Epoch.isObject(dataFormat) + return data unless dataFormat.name? and Epoch.isString(dataFormat.name) + return data unless Epoch.Data.Format[dataFormat.name]? + + args = [dataFormat.name, data] + if dataFormat.arguments? and Epoch.isArray(dataFormat.arguments) + args.push(a) for a in dataFormat.arguments + + if dataFormat.options? + opts = dataFormat.options + if type? + opts.type ?= type + args.push opts + else if type? + args.push {type: type} + + Epoch.data.apply(Epoch.data, args) + +# Method used to format incoming entries for real-time charts. +Epoch.Data.formatEntry = (datum, type, format) -> + return datum unless format? + + if Epoch.isString(format) + opts = { type: type } + return Epoch.Data.Format[format].entry datum, opts + + return datum unless Epoch.isObject(format) + return datum unless format.name? and Epoch.isString(format.name) + return datum unless Epoch.Data.Format[format.name]? + + dataFormat = Epoch.Util.defaults format, {} + + args = [datum] + if dataFormat.arguments? and Epoch.isArray(dataFormat.arguments) + args.push(a) for a in dataFormat.arguments + + if dataFormat.options? + opts = dataFormat.options + opts.type = type + args.push opts + else if type? + args.push {type: type} + + entry = Epoch.Data.Format[dataFormat.name].entry + entry.apply entry, args diff --git a/debian/missing-sources/epoch/src/epoch.coffee b/debian/missing-sources/epoch/src/epoch.coffee new file mode 100644 index 0000000..b2d06ca --- /dev/null +++ b/debian/missing-sources/epoch/src/epoch.coffee @@ -0,0 +1,17 @@ +window.Epoch ?= {} +window.Epoch.Chart ?= {} +window.Epoch.Time ?= {} +window.Epoch.Util ?= {} +window.Epoch.Formats ?= {} + +# Sends a warning to the developer console with the given message. +# @param [String] msg Message for the warning. +Epoch.warn = (msg) -> + (console.warn or console.log)("Epoch Warning: #{msg}") + +# Raises an exception with the given message (with the 'Epoch Error:' preamble). +# @param [String] msg Specific message for the exception. +Epoch.exception = (msg) -> + throw "Epoch Error: #{msg}" + +# "I think, baby, I was born just a little late!" -- Middle Class Rut diff --git a/debian/missing-sources/epoch/src/model.coffee b/debian/missing-sources/epoch/src/model.coffee new file mode 100644 index 0000000..76f6acd --- /dev/null +++ b/debian/missing-sources/epoch/src/model.coffee @@ -0,0 +1,55 @@ +# Data model for epoch charts. By instantiating a model and passing it to each +# of the charts on a page the application programmer can update data once and +# have each of the charts respond accordingly. +# +# In addition to setting basic / historical data via the setData method, the +# model also supports the push method, which when used will cause real-time +# plots to automatically update and animate. +class Epoch.Model extends Epoch.Events + defaults = + dataFormat: null + + # Creates a new Model. + # @option options dataFormat The default data fromat for the model. + # @option data Initial data for the model. + constructor: (options={}) -> + super() + options = Epoch.Util.defaults options, defaults + @dataFormat = options.dataFormat + @data = options.data + @loading = false + + # Sets the model's data. + # @param data Data to set for the model. + # @event data:updated Instructs listening charts that new data is available. + setData: (data) -> + @data = data + @trigger 'data:updated' + + # Pushes a new entry into the model. + # @param entry Entry to push. + # @event data:push Instructs listening charts that a new data entry is available. + push: (entry) -> + @entry = entry + @trigger 'data:push' + + # Determines if the model has data. + # @return true if the model has data, false otherwise. + hasData: -> + @data? + + # Retrieves and formats adata for the specific chart type and data format. + # @param [String] type Type of the chart for which to fetch the data. + # @param [String, Object] dataFormat (optional) Used to override the model's default data format. + # @return The model's data formatted based the parameters. + getData: (type, dataFormat) -> + dataFormat ?= @dataFormat + Epoch.Data.formatData @data, type, dataFormat + + # Retrieves the latest data entry that was pushed into the model. + # @param [String] type Type of the chart for which to fetch the data. + # @param [String, Object] dataFormat (optional) Used to override the model's default data format. + # @return The model's next data entry formatted based the parameters. + getNext: (type, dataFormat) -> + dataFormat ?= @dataFormat + Epoch.Data.formatEntry @entry, type, dataFormat diff --git a/debian/missing-sources/epoch/src/time.coffee b/debian/missing-sources/epoch/src/time.coffee new file mode 100644 index 0000000..402cc50 --- /dev/null +++ b/debian/missing-sources/epoch/src/time.coffee @@ -0,0 +1,615 @@ +# Real-time Plot Base Class. Uses an html5 canvas to recreate the basic d3 drawing routines +# while simultaneously reducing the load on the viewer's cpu (and, you know, not leaking +# memory which ultimately leads to a crashed browser). +# +# The class also handles the creation of axes and margins common to all time-series plots. +# Furthermore it layers the canvas below an SVG element to keep visual consistency when +# rendering text, glyphs, etc. +class Epoch.Time.Plot extends Epoch.Chart.Canvas + defaults = + range: null + fps: 24 + historySize: 120 + windowSize: 40 + queueSize: 10 + axes: ['bottom'] + ticks: + time: 15 + left: 5 + right: 5 + tickFormats: + top: Epoch.Formats.seconds + bottom: Epoch.Formats.seconds + left: Epoch.Formats.si + right: Epoch.Formats.si + + defaultAxisMargins = + top: 25 + right: 50 + bottom: 25 + left: 50 + + optionListeners = + 'option:margins': 'marginsChanged' + 'option:margins.top': 'marginsChanged' + 'option:margins.right': 'marginsChanged' + 'option:margins.bottom': 'marginsChanged' + 'option:margins.left': 'marginsChanged' + 'option:axes': 'axesChanged' + 'option:ticks': 'ticksChanged' + 'option:ticks.top': 'ticksChanged' + 'option:ticks.right': 'ticksChanged' + 'option:ticks.bottom': 'ticksChanged' + 'option:ticks.left': 'ticksChanged' + 'option:tickFormats': 'tickFormatsChanged' + 'option:tickFormats.top': 'tickFormatsChanged' + 'option:tickFormats.right': 'tickFormatsChanged' + 'option:tickFormats.bottom': 'tickFormatsChanged' + 'option:tickFormats.left': 'tickFormatsChanged' + + # Creates a new real-time plot. + # + # @param [Object] options Options for the plot. + # @option options [Integer] fps Number of frames per second to use when animating + # the plot. + # @option options [Integer] historySize Maximum number of elements to keep in history + # for the plot. + # @option options [Integer] windowSize Number of entries to simultaneously display + # when rendering the visualization. + # @option options [Integer] queueSize Number of elements to queue while not animating + # but still recieving elements. In some browsers, intervals will not fire if the + # page containing them is not the active tab. By setting a maximum limit to the + # number of unprocessed data points we can ensure that the memory footprint of the + # page does not get out of hand. + # @option options [Object] margins Explicit margins to use for the visualization. Note + # that these are optional and will be automatically generated based on which axes are + # used for the visualization. Margins are keyed by their position (top, left, bottom + # and/or right) and should map to [Integer] values. + # @option options [Array] axes Which axes to display when rendering the visualization + # (top, left, bottom, and/or right). + # @option options [Object] ticks Number of ticks to display on each axis available axes + # ares: time, left, and right. The number provided for the left and right axes are in + # absolute terms (i.e. there will be exactly that number of ticks). The time ticks + # denote how often a tick should be generated (e.g. if 5 is provided then a tick will + # be added every fifth time you push a new data entry into the visualization). + # @option options [Object] tickFormats Formatting functions for ticks on the given axes. + # The avaiable axes are: top, bottom, left, and right. + constructor: (@options) -> + givenMargins = Epoch.Util.copy(@options.margins) or {} + super(@options = Epoch.Util.defaults(@options, defaults)) + + if @options.model + @options.model.on 'data:push', => @pushFromModel() + + # Queue entering data to get around memory bloat and "non-active" tab issues + @_queue = [] + + # Margins + @margins = {} + for pos in ['top', 'right', 'bottom', 'left'] + @margins[pos] = if @options.margins? and @options.margins[pos]? + @options.margins[pos] + else if @hasAxis(pos) + defaultAxisMargins[pos] + else + 6 + + # SVG Overlay + @svg = @el.insert('svg', ':first-child') + .attr('width', @width) + .attr('height', @height) + .style('z-index', '1000') + + # Position the canvas "under" the SVG element + if @el.style('position') != 'absolute' and @el.style('position') != 'relative' + @el.style('position', 'relative') + + @canvas.style { position: 'absolute', 'z-index': '999' } + @_sizeCanvas() + + # Animation / Transitions + @animation = + interval: null + active: false + delta: => -(@w() / @options.fps), + tickDelta: => -( (@w() / @pixelRatio) / @options.fps ) + frame: 0, + duration: @options.fps + + # Add SVG Axes + @_buildAxes() + + # Callback used for animation + @animationCallback = => @_animate() + + # Listen for specific option changes + @onAll optionListeners + + # Positions and sizes the canvas based on margins and axes. + _sizeCanvas: -> + @canvas.attr + width: @innerWidth() + height: @innerHeight() + + @canvas.style + width: "#{@innerWidth() / @pixelRatio}px" + height: "#{@innerHeight() / @pixelRatio}px" + top: "#{@margins.top}px" + left: "#{@margins.left}px" + + # Removes any axes found in the SVG and adds both the time and range axes to the plot. + _buildAxes: -> + @svg.selectAll('.axis').remove() + @_prepareTimeAxes() + @_prepareRangeAxes() + + # Works exactly as in Epoch.Chart.Base with the addition of truncating value arrays + # to that of the historySize defined in the chart's options. + _annotateLayers: (prepared) -> + data = [] + for own i, layer of prepared + copy = Epoch.Util.copy(layer) + start = Math.max(0, layer.values.length - @options.historySize) + copy.values = layer.values.slice(start) + classes = ['layer'] + classes.push "category#{(i|0)+1}" + classes.push(Epoch.Util.dasherize layer.label) if layer.label? + copy.className = classes.join(' ') + copy.visible = true + data.push copy + return data + + # This method is called to provide a small offset for placement of horizontal ticks. + # The value returned will be added to the x value of each tick as they are being + # rendered. + # + # @return [Number] The horizontal offset for the top and bottom axes ticks. + _offsetX: -> 0 + + # Builds time axes (bottom and top) + _prepareTimeAxes: -> + if @hasAxis('bottom') + axis = @bottomAxis = @svg.append('g') + .attr('class', "x axis bottom canvas") + .attr('transform', "translate(#{@margins.left-1}, #{@innerHeight()/@pixelRatio+@margins.top})") + axis.append('path') + .attr('class', 'domain') + .attr('d', "M0,0H#{@innerWidth()/@pixelRatio+1}") + + if @hasAxis('top') + axis = @topAxis = @svg.append('g') + .attr('class', "x axis top canvas") + .attr('transform', "translate(#{@margins.left-1}, #{@margins.top})") + axis.append('path') + .attr('class', 'domain') + .attr('d', "M0,0H#{@innerWidth()/@pixelRatio+1}") + + @_resetInitialTimeTicks() + + # Resets the initial ticks for the time axes. + _resetInitialTimeTicks: -> + tickInterval = @options.ticks.time + @_ticks = [] + @_tickTimer = tickInterval + + @bottomAxis.selectAll('.tick').remove() if @bottomAxis? + @topAxis.selectAll('.tick').remove() if @topAxis? + + for layer in @data + continue unless Epoch.isNonEmptyArray(layer.values) + [i, k] = [@options.windowSize-1, layer.values.length-1] + while i >= 0 and k >= 0 + @_pushTick i, layer.values[k].time, false, true + i -= tickInterval + k -= tickInterval + break + + # Builds the range axes (left and right) + _prepareRangeAxes: -> + if @hasAxis('left') + @svg.append("g") + .attr("class", "y axis left") + .attr('transform', "translate(#{@margins.left-1}, #{@margins.top})") + .call(@leftAxis()) + + if @hasAxis('right') + @svg.append('g') + .attr('class', 'y axis right') + .attr('transform', "translate(#{@width - @margins.right}, #{@margins.top})") + .call(@rightAxis()) + + # @return [Object] The d3 left axis. + leftAxis: -> + ticks = @options.ticks.left + axis = d3.svg.axis().scale(@ySvgLeft()).orient('left') + .tickFormat(@options.tickFormats.left) + if ticks == 2 + axis.tickValues @extent((d) -> d.y) + else + axis.ticks(ticks) + + # @return [Object] The d3 right axis. + rightAxis: -> + extent = @extent((d) -> d.y) + ticks = @options.ticks.right + axis = d3.svg.axis().scale(@ySvgRight()).orient('right') + .tickFormat(@options.tickFormats.right) + if ticks == 2 + axis.tickValues @extent((d) -> d.y) + else + axis.ticks(ticks) + + # Determines if the visualization is displaying the axis with the given name. + # @param [String] name Name of the axis + # @return [Boolean] <code>true</code> if the axis was set in the options, <code>false</code> otherwise. + hasAxis: (name) -> + @options.axes.indexOf(name) > -1 + + # @return [Number] the width of the visualization area of the plot (full width - margins) + innerWidth: -> + (@width - (@margins.left + @margins.right)) * @pixelRatio + + # @return [Number] the height of the visualization area of the plot (full height - margins) + innerHeight: -> + (@height - (@margins.top + @margins.bottom)) * @pixelRatio + + # Abstract method for performing any preprocessing before queuing new entries + # @param entry [Object] The entry to prepare. + # @return [Object] The prepared entry. + _prepareEntry: (entry) -> entry + + # Abstract method for preparing a group of layered entries entering the visualization + # @param [Array] layers The layered entries to prepare. + # @return [Array] The prepared layers. + _prepareLayers: (layers) -> layers + + # This method will remove the first incoming entry from the visualization's queue + # and shift it into the working set (aka window). It then starts the animating the + # transition of the element into the visualization. + # @event transition:start in the case that animation is actually started. + _startTransition: -> + return if @animation.active == true or @_queue.length == 0 + @trigger 'transition:start' + @_shift() + @animation.active = true + @animation.interval = setInterval(@animationCallback, 1000/@options.fps) + + # Stops animating and clears the animation interval given there is no more + # incoming data to process. Also finalizes tick entering and exiting. + # @event transition:end After the transition has completed. + _stopTransition: -> + return unless @inTransition() + + # Shift data off the end + for layer in @data + continue unless layer.values.length > @options.windowSize + 1 + layer.values.shift() + + # Finalize tick transitions + [firstTick, lastTick] = [@_ticks[0], @_ticks[@_ticks.length-1]] + + if lastTick? and lastTick.enter + lastTick.enter = false + lastTick.opacity = 1 + + if firstTick? and firstTick.exit + @_shiftTick() + + # Reset the animation frame modulus + @animation.frame = 0 + + # Trigger that we are done transitioning + @trigger 'transition:end' + + # Clear the transition interval unless another entry is already queued + if @_queue.length > 0 + @_shift() + else + @animation.active = false + clearInterval @animation.interval + + # Determines if the plot is currently animating a transition. + # @return [Boolean] <code>true</code> if the plot is animating, <code>false</code> otherwise. + inTransition: -> + @animation.active + + # This method is used by the application programmer to introduce new data into + # the timeseries plot. The method queues the incoming data, ensures a fixed size + # for the data queue, and finally calls <code>_startTransition</code> method to + # begin animating the plot. + # @param [Array] layers Layered incoming visualization data. + # @event push Triggered after the new data has been pushed into the queue. + push: (layers) -> + layers = @_prepareLayers(layers) + + # Handle entry queue maximum size + if @_queue.length > @options.queueSize + @_queue.splice @options.queueSize, (@_queue.length - @options.queueSize) + return false if @_queue.length == @options.queueSize + + # Push the entry into the queue + @_queue.push layers.map((entry) => @_prepareEntry(entry)) + + @trigger 'push' + + # Begin the transition unless we are already doing so + @_startTransition() unless @inTransition() + + # Fetches new entry data from the model in response to a 'data:push' event. + pushFromModel: -> + @push @options.model.getNext(@options.type, @options.dataFormat) + + # Shift elements off the incoming data queue (see the implementation of + # push above). + # + # If there's data to be shoved into the visualization it will pull it + # off the queue and put it into the working dataset. It also calls through + # to @_updateTicks to handle horizontal (or "time") axes tick transitions + # since we're implementing independent of d3 as well. + # + # @event before:shift Before an element has been shifted off the queue. + # @event after:shift After the element has been shifted off the queue. + _shift: -> + @trigger 'before:shift' + entry = @_queue.shift() + layer.values.push(entry[i]) for own i, layer of @data + @_updateTicks(entry[0].time) + @_transitionRangeAxes() + @trigger 'after:shift' + + # Transitions the left and right axes when the range of the plot has changed. + _transitionRangeAxes: -> + if @hasAxis('left') + @svg.selectAll('.y.axis.left').transition() + .duration(500) + .ease('linear') + .call(@leftAxis()) + + if @hasAxis('right') + @svg.selectAll('.y.axis.right').transition() + .duration(500) + .ease('linear') + .call(@rightAxis()) + + # Performs the animation for transitioning elements in the visualization. + _animate: -> + return unless @inTransition() + @_stopTransition() if ++@animation.frame == @animation.duration + @draw(@animation.frame * @animation.delta()) + @_updateTimeAxes() + + # @param [Array] givenDomain A given domain for the scale + # @return [Function] The y scale for the plot + y: (givenDomain) -> + d3.scale.linear() + .domain(@_getScaleDomain(givenDomain)) + .range([@innerHeight(), 0]) + + # @param [Array] givenDomain Optional domain to override default + # @return [Function] The y scale for the svg portions of the plot + ySvg: (givenDomain) -> + d3.scale.linear() + .domain(@_getScaleDomain(givenDomain)) + .range([@innerHeight() / @pixelRatio, 0]) + + # @return [Function] The y scale for the svg portion of the plot for the left axis + ySvgLeft: -> + if @options.range? + @ySvg @options.range.left + else + @ySvg() + + # @return [Function] The y scale for the svg portion of the plot for the right axis + ySvgRight: -> + if @options.range? + @ySvg @options.range.right + else + @ySvg() + + # @return [Number] The width of a single section of the graph pertaining to a data point + w: -> + @innerWidth() / @options.windowSize + + # This is called every time we introduce new data (as a result of _shift) + # it checks to see if we also need to update the working tick set and + # makes the approriate changes for handling tick animation (enter, exit, + # and update in the d3 model). + # + # @param [Integer] newTime Current newest timestamp in the data + _updateTicks: (newTime) -> + return unless @hasAxis('top') or @hasAxis('bottom') + + # Incoming ticks + unless (++@_tickTimer) % @options.ticks.time + @_pushTick(@options.windowSize, newTime, true) + + # Outgoing ticks + return unless @_ticks.length > 0 + unless @_ticks[0].x - (@w()/@pixelRatio) >= 0 + @_ticks[0].exit = true + + # Makes and pushes a new tick into the visualization. + # + # @param bucket Index in the data window where the tick should initially be position + # @param time The unix timestamp associated with the tick + # @param enter Whether or not the tick should be considered as "newly entering" + # Used primarily for performing the tick opacity tween. + _pushTick: (bucket, time, enter=false, reverse=false) -> + return unless @hasAxis('top') or @hasAxis('bottom') + tick = + time: time + x: bucket*(@w()/@pixelRatio) + @_offsetX() + opacity: if enter then 0 else 1 + enter: if enter then true else false + exit: false + + if @hasAxis('bottom') + g = @bottomAxis.append('g') + .attr('class', 'tick major') + .attr('transform', "translate(#{tick.x+1},0)") + .style('opacity', tick.opacity) + + g.append('line') + .attr('y2', 6) + + g.append('text') + .attr('text-anchor', 'middle') + .attr('dy', 19) + .text(@options.tickFormats.bottom(tick.time)) + + tick.bottomEl = g + + if @hasAxis('top') + g = @topAxis.append('g') + .attr('class', 'tick major') + .attr('transform', "translate(#{tick.x+1},0)") + .style('opacity', tick.opacity) + + g.append('line') + .attr('y2', -6) + + g.append('text') + .attr('text-anchor', 'middle') + .attr('dy', -10) + .text(@options.tickFormats.top(tick.time)) + + tick.topEl = g + + if reverse + @_ticks.unshift tick + else + @_ticks.push tick + return tick + + # Shifts a tick that is no longer needed out of the visualization. + _shiftTick: -> + return unless @_ticks.length > 0 + tick = @_ticks.shift() + tick.topEl.remove() if tick.topEl? + tick.bottomEl.remove() if tick.bottomEl? + + # This performs animations for the time axes (top and bottom). + _updateTimeAxes: -> + return unless @hasAxis('top') or @hasAxis('bottom') + [dx, dop] = [@animation.tickDelta(), 1 / @options.fps] + + for tick in @_ticks + tick.x += dx + if @hasAxis('bottom') + tick.bottomEl.attr('transform', "translate(#{tick.x+1},0)") + if @hasAxis('top') + tick.topEl.attr('transform', "translate(#{tick.x+1},0)") + + if tick.enter + tick.opacity += dop + else if tick.exit + tick.opacity -= dop + + if tick.enter or tick.exit + tick.bottomEl.style('opacity', tick.opacity) if @hasAxis('bottom') + tick.topEl.style('opacity', tick.opacity) if @hasAxis('top') + + # Draws the visualization in the plot's canvas. + # @param delta The current x offset to apply to all elements when rendering. This number + # will be 0 when the plot is not animating and negative when it is. + # @abstract It does nothing on its own but is provided so that subclasses can + # define a custom rendering routine. + draw: (delta=0) -> super() + + dimensionsChanged: -> + super() + @svg.attr('width', @width).attr('height', @height) + @_sizeCanvas() + @_buildAxes() + @draw(@animation.frame * @animation.delta()) + + # Updates axes in response to an <code>option:axes</code> event. + axesChanged: -> + for pos in ['top', 'right', 'bottom', 'left'] + continue if @options.margins? and @options.margins[pos]? + if @hasAxis(pos) + @margins[pos] = defaultAxisMargins[pos] + else + @margins[pos] = 6 + @_sizeCanvas() + @_buildAxes() + @draw(@animation.frame * @animation.delta()) + + # Updates ticks in response to an <code>option.ticks.*</code> event. + ticksChanged: -> + @_resetInitialTimeTicks() + @_transitionRangeAxes() + @draw(@animation.frame * @animation.delta()) + + # Updates tick formats in response to an <code>option.tickFormats.*</code> event. + tickFormatsChanged: -> + @_resetInitialTimeTicks() + @_transitionRangeAxes() + @draw(@animation.frame * @animation.delta()) + + # Updates margins in response to an <code>option.margins.*</code> event. + marginsChanged: -> + return unless @options.margins? + for own pos, size of @options.margins + unless size? + @margins[pos] = 6 + else + @margins[pos] = size + + @_sizeCanvas() + @draw(@animation.frame * @animation.delta()) + + layerChanged: -> + @_transitionRangeAxes() + super() + + +# Base class for all "stacked" plot types (e.g. bar charts, area charts, etc.) +# @abstract It does not perform rendering but instead formats the data +# so as to ease the process of rendering stacked plots. +class Epoch.Time.Stack extends Epoch.Time.Plot + # Sets stacking information (y0) for each of the points in each layer + _stackLayers: -> + return unless (layers = @getVisibleLayers()).length > 0 + for i in [0...layers[0].values.length] + y0 = 0 + for layer in layers + layer.values[i].y0 = y0 + y0 += layer.values[i].y + + # Adds stacking information for layers entering the visualization. + # @param [Array] layers Layers to stack. + _prepareLayers: (layers) -> + y0 = 0 + for own i, d of layers + continue unless @data[i].visible + d.y0 = y0 + y0 += d.y + return layers + + # Ensures that elements are stacked when setting the initial data. + # @param [Array] data Layered data to set for the visualization. + setData: (data) -> + super(data) + @_stackLayers() + + # Finds the correct extent to use for range axes (left and right). + # @return [Array] An extent array with the first element equal to 0 + # and the second element equal to the maximum value amongst the + # stacked entries. + extent: -> + [max, layers] = [0, @getVisibleLayers()] + return [0, 0] unless layers.length + + for i in [0...layers[0].values.length] + sum = 0 + for j in [0...layers.length] + sum += layers[j].values[i].y + max = sum if sum > max + + [0, max] + + layerChanged: -> + @_stackLayers() + @_prepareLayers(layers) for layers in @_queue + super() diff --git a/debian/missing-sources/epoch/src/time/area.coffee b/debian/missing-sources/epoch/src/time/area.coffee new file mode 100644 index 0000000..22bf9db --- /dev/null +++ b/debian/missing-sources/epoch/src/time/area.coffee @@ -0,0 +1,80 @@ + +# Real-time stacked area chart implementation. +class Epoch.Time.Area extends Epoch.Time.Stack + constructor: (@options={}) -> + @options.type ?= 'time.area' + super(@options) + @draw() + + # Sets the appropriate styles to the graphics context given a particular layer. + # @param [Object] layer Layer for which to set the styles. + setStyles: (layer) -> + if layer? && layer.className? + styles = @getStyles "g.#{layer.className.replace(/\s/g,'.')} path.area" + else + styles = @getStyles "g path.area" + @ctx.fillStyle = styles.fill + if styles.stroke? + @ctx.strokeStyle = styles.stroke + if styles['stroke-width']? + @ctx.lineWidth = styles['stroke-width'].replace('px', '') + + # Draws areas for the chart + _drawAreas: (delta=0) -> + [y, w, layers] = [@y(), @w(), @getVisibleLayers()] + + for i in [layers.length-1..0] + continue unless (layer = layers[i]) + + @setStyles layer + @ctx.beginPath() + + [j, k, trans] = [@options.windowSize, layer.values.length, @inTransition()] + firstX = null + while (--j >= -2) and (--k >= 0) + entry = layer.values[k] + args = [(j+1)*w+delta, y(entry.y + entry.y0)] + args[0] += w if trans + if i == @options.windowSize - 1 + @ctx.moveTo.apply @ctx, args + else + @ctx.lineTo.apply @ctx, args + + if trans + borderX = (j+3)*w+delta + else + borderX = (j+2)*w+delta + + @ctx.lineTo(borderX, @innerHeight()) + @ctx.lineTo(@width*@pixelRatio+w+delta, @innerHeight()) + @ctx.closePath() + @ctx.fill() + + # Draws strokes for the chart + _drawStrokes: (delta=0) -> + [y, w, layers] = [@y(), @w(), @getVisibleLayers()] + + for i in [layers.length-1..0] + continue unless (layer = layers[i]) + @setStyles layer + @ctx.beginPath() + + [i, k, trans] = [@options.windowSize, layer.values.length, @inTransition()] + firstX = null + while (--i >= -2) and (--k >= 0) + entry = layer.values[k] + args = [(i+1)*w+delta, y(entry.y + entry.y0)] + args[0] += w if trans + if i == @options.windowSize - 1 + @ctx.moveTo.apply @ctx, args + else + @ctx.lineTo.apply @ctx, args + + @ctx.stroke() + + # Draws the area chart. + draw: (delta=0) -> + @clear() + @_drawAreas(delta) + @_drawStrokes(delta) + super() diff --git a/debian/missing-sources/epoch/src/time/bar.coffee b/debian/missing-sources/epoch/src/time/bar.coffee new file mode 100644 index 0000000..7bcb7be --- /dev/null +++ b/debian/missing-sources/epoch/src/time/bar.coffee @@ -0,0 +1,48 @@ + +# Real-time Bar Chart implementation. +class Epoch.Time.Bar extends Epoch.Time.Stack + constructor: (@options={}) -> + @options.type ?= 'time.bar' + super(@options) + @draw() + + # @return [Number] An offset used to align the ticks to the center of the rendered bars. + _offsetX: -> + 0.5 * @w() / @pixelRatio + + # Sets the styles for the graphics context given a layer class name. + # @param [String] className The class name to use when deriving the styles. + setStyles: (className) -> + styles = @getStyles "rect.bar.#{className.replace(/\s/g,'.')}" + @ctx.fillStyle = styles.fill + + if !styles.stroke? or styles.stroke == 'none' + @ctx.strokeStyle = 'transparent' + else + @ctx.strokeStyle = styles.stroke + + if styles['stroke-width']? + @ctx.lineWidth = styles['stroke-width'].replace('px', '') + + # Draws the stacked bar chart. + draw: (delta=0) -> + @clear() + [y, w] = [@y(), @w()] + + for layer in @getVisibleLayers() + continue unless Epoch.isNonEmptyArray(layer.values) + @setStyles(layer.className) + + [i, k, trans] = [@options.windowSize, layer.values.length, @inTransition()] + iBoundry = if trans then -1 else 0 + + while (--i >= iBoundry) and (--k >= 0) + entry = layer.values[k] + [ex, ey, ey0] = [i*w+delta, entry.y, entry.y0] + ex += w if trans + args = [ex+1, y(ey+ey0), w-2, @innerHeight()-y(ey)+0.5*@pixelRatio] + + @ctx.fillRect.apply(@ctx, args) + @ctx.strokeRect.apply(@ctx, args) + + super() diff --git a/debian/missing-sources/epoch/src/time/gauge.coffee b/debian/missing-sources/epoch/src/time/gauge.coffee new file mode 100644 index 0000000..8efadb2 --- /dev/null +++ b/debian/missing-sources/epoch/src/time/gauge.coffee @@ -0,0 +1,209 @@ + +# Real-time Gauge Visualization. Note: Looks best with a 4:3 aspect ratio (w:h) +class Epoch.Time.Gauge extends Epoch.Chart.Canvas + defaults = + type: 'time.gauge' + domain: [0, 1] + ticks: 10 + tickSize: 5 + tickOffset: 5 + fps: 34 + format: Epoch.Formats.percent + + optionListeners = + 'option:domain': 'domainChanged' + 'option:ticks': 'ticksChanged' + 'option:tickSize': 'tickSizeChanged' + 'option:tickOffset': 'tickOffsetChanged' + 'option:format': 'formatChanged' + + # Creates the new gauge chart. + # @param [Object] options Options for the gauge chart. + # @option options [Array] domain The domain to use when rendering values (default: [0, 1]). + # @option options [Integer] ticks Number of ticks to render (default: 10). + # @option options [Integer] tickSize The length (in pixels) for each tick (default: 5). + # @option options [Integer] tickOffset The number of pixels by which to offset ticks from the outer arc (default: 5). + # @option options [Integer] fps The number of animation frames to render per second (default: 34). + # @option options [Function] format The formatting function to use when rendering the gauge label + # (default: Epoch.Formats.percent). + constructor: (@options={}) -> + super(@options = Epoch.Util.defaults(@options, defaults)) + @value = @options.value or 0 + + if @options.model + @options.model.on 'data:push', => @pushFromModel() + + # SVG Labels Overlay + if @el.style('position') != 'absolute' and @el.style('position') != 'relative' + @el.style('position', 'relative') + + @svg = @el.insert('svg', ':first-child') + .attr('width', @width) + .attr('height', @height) + .attr('class', 'gauge-labels') + + @svg.style + 'position': 'absolute' + 'z-index': '1' + + @svg.append('g') + .attr('transform', "translate(#{@textX()}, #{@textY()})") + .append('text') + .attr('class', 'value') + .text(@options.format(@value)) + + # Animations + @animation = + interval: null + active: false + delta: 0 + target: 0 + + @_animate = => + if Math.abs(@animation.target - @value) < Math.abs(@animation.delta) + @value = @animation.target + clearInterval @animation.interval + @animation.active = false + else + @value += @animation.delta + + @svg.select('text.value').text(@options.format(@value)) + @draw() + + @onAll optionListeners + @draw() + + # Sets the value for the gauge to display and begins animating the guage. + # @param [Number] value Value to set for the gauge. + update: (value) -> + @animation.target = value + @animation.delta = (value - @value) / @options.fps + unless @animation.active + @animation.interval = setInterval @_animate, (1000/@options.fps) + @animation.active = true + + # Alias for the <code>update()</code> method. + # @param [Number] value Value to set for the gauge. + push: (value) -> + @update value + + # Responds to a model's 'data:push' event. + pushFromModel: -> + next = @options.model.getNext(@options.type, @options.dataFormat) + @update next + + # @return [Number] The radius for the gauge. + radius: -> @getHeight() / 1.58 + + # @return [Number] The center position x-coordinate for the gauge. + centerX: -> @getWidth() / 2 + + # @return [Number] The center position y-coordinate for the gauge. + centerY: -> 0.68 * @getHeight() + + # @return [Number] The x-coordinate for the gauge text display. + textX: -> @width / 2 + + # @return [Number] The y-coordinate for the gauge text display. + textY: -> 0.48 * @height + + # @return [Number] The angle to set for the needle given a value within the domain. + # @param [Number] value Value to translate into a needle angle. + getAngle: (value) -> + [a, b] = @options.domain + ((value - a) / (b - a)) * (Math.PI + 2*Math.PI/8) - Math.PI/2 - Math.PI/8 + + # Sets context styles given a particular selector. + # @param [String] selector The selector to use when setting the styles. + setStyles: (selector) -> + styles = @getStyles selector + @ctx.fillStyle = styles.fill + @ctx.strokeStyle = styles.stroke + @ctx.lineWidth = styles['stroke-width'].replace('px', '') if styles['stroke-width']? + + # Draws the gauge. + draw: -> + [cx, cy, r] = [@centerX(), @centerY(), @radius()] + [tickOffset, tickSize] = [@options.tickOffset, @options.tickSize] + + @clear() + + # Draw Ticks + t = d3.scale.linear() + .domain([0, @options.ticks]) + .range([ -(9/8)*Math.PI, Math.PI/8 ]) + + @setStyles '.epoch .gauge .tick' + @ctx.beginPath() + for i in [0..@options.ticks] + a = t(i) + [c, s] = [Math.cos(a), Math.sin(a)] + + x1 = c * (r-tickOffset) + cx + y1 = s * (r-tickOffset) + cy + x2 = c * (r-tickOffset-tickSize) + cx + y2 = s * (r-tickOffset-tickSize) + cy + + @ctx.moveTo x1, y1 + @ctx.lineTo x2, y2 + + @ctx.stroke() + + # Outer arc + @setStyles '.epoch .gauge .arc.outer' + @ctx.beginPath() + @ctx.arc cx, cy, r, -(9/8)*Math.PI, (1/8)*Math.PI, false + @ctx.stroke() + + # Inner arc + @setStyles '.epoch .gauge .arc.inner' + @ctx.beginPath() + @ctx.arc cx, cy, r-10, -(9/8)*Math.PI, (1/8)*Math.PI, false + @ctx.stroke() + + @drawNeedle() + + super() + + # Draws the needle. + drawNeedle: -> + [cx, cy, r] = [@centerX(), @centerY(), @radius()] + ratio = @value / @options.domain[1] + + @setStyles '.epoch .gauge .needle' + @ctx.beginPath() + @ctx.save() + @ctx.translate cx, cy + @ctx.rotate @getAngle(@value) + + @ctx.moveTo 4 * @pixelRatio, 0 + @ctx.lineTo -4 * @pixelRatio, 0 + @ctx.lineTo -1 * @pixelRatio, 19-r + @ctx.lineTo 1, 19-r + @ctx.fill() + + @setStyles '.epoch .gauge .needle-base' + @ctx.beginPath() + @ctx.arc 0, 0, (@getWidth() / 25), 0, 2*Math.PI + @ctx.fill() + + @ctx.restore() + + # Correctly responds to an <code>option:</code> + domainChanged: -> @draw() + + # Correctly responds to an <code>option:</code> + ticksChanged: -> @draw() + + # Correctly responds to an <code>option:</code> + tickSizeChanged: -> @draw() + + # Correctly responds to an <code>option:</code> + tickOffsetChanged: -> @draw() + + # Correctly responds to an <code>option:</code> + formatChanged: -> @svg.select('text.value').text(@options.format(@value)) + + + +# "The mother of a million sons... CIVILIZATION!" -- Justice diff --git a/debian/missing-sources/epoch/src/time/heatmap.coffee b/debian/missing-sources/epoch/src/time/heatmap.coffee new file mode 100644 index 0000000..d7f2c50 --- /dev/null +++ b/debian/missing-sources/epoch/src/time/heatmap.coffee @@ -0,0 +1,261 @@ + +# Real-time Heatmap Implementation. +class Epoch.Time.Heatmap extends Epoch.Time.Plot + defaults = + type: 'time.heatmap' + buckets: 10 + bucketRange: [0, 100] + opacity: 'linear' + bucketPadding: 2 + paintZeroValues: false + cutOutliers: false + + # Easy to use "named" color functions + colorFunctions = + root: (value, max) -> Math.pow(value/max, 0.5) + linear: (value, max) -> value / max + quadratic: (value, max) -> Math.pow(value/max, 2) + cubic: (value, max) -> Math.pow(value/max, 3) + quartic: (value, max) -> Math.pow(value/max, 4) + quintic: (value, max) -> Math.pow(value/max, 5) + + optionListeners = + 'option:buckets': 'bucketsChanged' + 'option:bucketRange': 'bucketRangeChanged' + 'option:opacity': 'opacityChanged' + 'option:bucketPadding': 'bucketPaddingChanged' + 'option:paintZeroValues': 'paintZeroValuesChanged' + 'option:cutOutliers': 'cutOutliersChanged' + + # Creates a new heatmap. + # @param [Object] options Options for the heatmap. + # @option options [Integer] buckets Number of vertical buckets to use when normalizing the + # incoming histogram data for visualization in the heatmap (default: 10). + # @option options [Array] bucketRange A range of acceptable values to be bucketed (default: [0, 100]). + # @option options [String, Function] opacity The opacity coloring function to use when rendering buckets + # in a column. The built-in functions (referenced by string) are: 'root', 'linear', 'quadratic', 'cubic', + # 'quartic', and 'quintic'. A custom function can be supplied given it accepts two parameters (value, max) + # and returns a numeric value from 0 to 1. Default: linear. + # @option options [Number] bucketPadding Amount of padding to apply around buckets (default: 2). + constructor: (@options={}) -> + super(@options = Epoch.Util.defaults(@options, defaults)) + @_setOpacityFunction() + @_setupPaintCanvas() + @onAll optionListeners + @draw() + + _setOpacityFunction: -> + if Epoch.isString(@options.opacity) + @_opacityFn = colorFunctions[@options.opacity] + Epoch.exception "Unknown coloring function provided '#{@options.opacity}'" unless @_opacityFn? + else if Epoch.isFunction(@options.opacity) + @_opacityFn = @options.opacity + else + Epoch.exception "Unknown type for provided coloring function." + + # Prepares initially set data for rendering. + # @param [Array] data Layered histogram data for the visualization. + setData: (data) -> + super(data) + for layer in @data + layer.values = layer.values.map((entry) => @_prepareEntry(entry)) + + # Distributes the full histogram in the entry into the defined buckets + # for the visualization. + # @param [Object] entry Entry to prepare for visualization. + _getBuckets: (entry) -> + prepared = + time: entry.time + max: 0 + buckets: (0 for i in [0...@options.buckets]) + + # Bucket size = (Range[1] - Range[0]) / number of buckets + bucketSize = (@options.bucketRange[1] - @options.bucketRange[0]) / @options.buckets + + for own value, count of entry.histogram + index = parseInt((value - @options.bucketRange[0]) / bucketSize) + + # Remove outliers from the preprared buckets if instructed to do so + if @options.cutOutliers and ((index < 0) or (index >= @options.buckets)) + continue + + # Bound the histogram to the range (aka, handle out of bounds values) + if index < 0 + index = 0 + else if index >= @options.buckets + index = @options.buckets - 1 + + prepared.buckets[index] += parseInt count + + for i in [0...prepared.buckets.length] + prepared.max = Math.max(prepared.max, prepared.buckets[i]) + + return prepared + + # @return [Function] The y scale for the heatmap. + y: -> + d3.scale.linear() + .domain(@options.bucketRange) + .range([@innerHeight(), 0]) + + # @return [Function] The y scale for the svg portions of the heatmap. + ySvg: -> + d3.scale.linear() + .domain(@options.bucketRange) + .range([@innerHeight() / @pixelRatio, 0]) + + # @return [Number] The height to render each bucket in a column (disregards padding). + h: -> + @innerHeight() / @options.buckets + + # @return [Number] The offset needed to center ticks at the middle of each column. + _offsetX: -> + 0.5 * @w() / @pixelRatio + + # Creates the painting canvas which is used to perform all the actual drawing. The contents + # of the canvas are then copied into the actual display canvas and through some image copy + # trickery at the end of a transition the illusion of motion over time is preserved. + # + # Using two canvases in this way allows us to render an incredible number of buckets in the + # visualization and animate them at high frame rates without smashing the cpu. + _setupPaintCanvas: -> + # Size the paint canvas to have a couple extra columns so we can perform smooth transitions + @paintWidth = (@options.windowSize + 1) * @w() + @paintHeight = @height * @pixelRatio + + # Create the "memory only" canvas and nab the drawing context + @paint = document.createElement('CANVAS') + @paint.width = @paintWidth + @paint.height = @paintHeight + @p = Epoch.Util.getContext @paint + + # Paint the initial data (rendering backwards from just before the fixed paint position) + @redraw() + + # Hook into the events to paint the next row after it's been shifted into the data + @on 'after:shift', '_paintEntry' + + # At the end of a transition we must reset the paint canvas by shifting the viewable + # buckets to the left (this allows for a fixed cut point and single renders below in @draw) + @on 'transition:end', '_shiftPaintCanvas' + @on 'transition:end', => @draw(@animation.frame * @animation.delta()) + + # Redraws the entire heatmap for the current data. + redraw: -> + return unless Epoch.isNonEmptyArray(@data) and Epoch.isNonEmptyArray(@data[0].values) + entryIndex = @data[0].values.length + drawColumn = @options.windowSize + + # This addresses a strange off-by-one issue when the chart is transitioning + drawColumn++ if @inTransition() + + while (--entryIndex >= 0) and (--drawColumn >= 0) + @_paintEntry(entryIndex, drawColumn) + @draw(@animation.frame * @animation.delta()) + + # Computes the correct color for a given bucket. + # @param [Integer] value Normalized value at the bucket. + # @param [Integer] max Normalized maximum for the column. + # @param [String] color Computed base color for the bucket. + _computeColor: (value, max, color) -> + Epoch.Util.toRGBA(color, @_opacityFn(value, max)) + + # Paints a single entry column on the paint canvas at the given column. + # @param [Integer] entryIndex Index of the entry to paint. + # @param [Integer] drawColumn Column on the paint canvas to place the visualized entry. + _paintEntry: (entryIndex=null, drawColumn=null) -> + [w, h] = [@w(), @h()] + + entryIndex ?= @data[0].values.length - 1 + drawColumn ?= @options.windowSize + + entries = [] + bucketTotals = (0 for i in [0...@options.buckets]) + maxTotal = 0 + + for layer in @getVisibleLayers() + entry = @_getBuckets( layer.values[entryIndex] ) + for own bucket, count of entry.buckets + bucketTotals[bucket] += count + maxTotal += entry.max + styles = @getStyles ".#{layer.className.split(' ').join('.')} rect.bucket" + entry.color = styles.fill + entries.push entry + + xPos = drawColumn * w + + @p.clearRect xPos, 0, w, @paintHeight + + j = @options.buckets + + for own bucket, sum of bucketTotals + color = @_avgLab(entries, bucket) + max = 0 + for entry in entries + max += (entry.buckets[bucket] / sum) * maxTotal + if sum > 0 or @options.paintZeroValues + @p.fillStyle = @_computeColor(sum, max, color) + @p.fillRect xPos, (j-1) * h, w-@options.bucketPadding, h-@options.bucketPadding + j-- + + # This shifts the image contents of the paint canvas to the left by 1 column width. + # It is called after a transition has ended (yay, slight of hand). + _shiftPaintCanvas: -> + data = @p.getImageData @w(), 0, @paintWidth-@w(), @paintHeight + @p.putImageData data, 0, 0 + + # Performs an averaging of the colors for muli-layer heatmaps using the lab color space. + # @param [Array] entries The layers for which the colors are to be averaged. + # @param [Number] bucket The bucket in the entries that must be averaged. + # @return [String] The css color code for the average of all the layer colors. + _avgLab: (entries, bucket) -> + [l, a, b, total] = [0, 0, 0, 0] + for entry in entries + continue unless entry.buckets[bucket]? + total += entry.buckets[bucket] + + for own i, entry of entries + if entry.buckets[bucket]? + value = entry.buckets[bucket]|0 + else + value = 0 + ratio = value / total + color = d3.lab(entry.color) + l += ratio * color.l + a += ratio * color.a + b += ratio * color.b + + d3.lab(l, a, b).toString() + + # Copies the paint canvas onto the display canvas, thus rendering the heatmap. + draw: (delta=0) -> + @clear() + @ctx.drawImage @paint, delta, 0 + super() + + # Changes the number of buckets in response to an <code>option:buckets</code> event. + bucketsChanged: -> @redraw() + + # Changes the range of the buckets in response to an <code>option:bucketRange</code> event. + bucketRangeChanged: -> + @_transitionRangeAxes() + @redraw() + + # Changes the opacity function in response to an <code>option:opacity</code> event. + opacityChanged: -> + @_setOpacityFunction() + @redraw() + + # Changes the bucket padding in response to an <code>option:bucketPadding</code> event. + bucketPaddingChanged: -> @redraw() + + # Changes whether or not to paint zeros in response to an <code>option:paintZeroValues</code> event. + paintZeroValuesChanged: -> @redraw() + + # Changes whether or not to cut outliers when bucketing in response to an + # <code>option:cutOutliers</code> event. + cutOutliersChanged: -> @redraw() + + layerChanged: -> @redraw() + +# "Audio... Audio... Audio... Video Disco..." - Justice diff --git a/debian/missing-sources/epoch/src/time/line.coffee b/debian/missing-sources/epoch/src/time/line.coffee new file mode 100644 index 0000000..8a4271b --- /dev/null +++ b/debian/missing-sources/epoch/src/time/line.coffee @@ -0,0 +1,39 @@ + +# Real-time line chart implementation +class Epoch.Time.Line extends Epoch.Time.Plot + constructor: (@options={}) -> + @options.type ?= 'time.line' + super(@options) + @draw() + + # Sets the graphics context styles based ont he given layer class name. + # @param [String] className The class name of the layer for which to set the styles. + setStyles: (className) -> + styles = @getStyles "g.#{className.replace(/\s/g,'.')} path.line" + @ctx.fillStyle = styles.fill + @ctx.strokeStyle = styles.stroke + @ctx.lineWidth = @pixelRatio * styles['stroke-width'].replace('px', '') + + # Draws the line chart. + draw: (delta=0) -> + @clear() + w = @w() + for layer in @getVisibleLayers() + continue unless Epoch.isNonEmptyArray(layer.values) + @setStyles(layer.className) + @ctx.beginPath() + y = @y(layer.range) + [i, k, trans] = [@options.windowSize, layer.values.length, @inTransition()] + + while (--i >= -2) and (--k >= 0) + entry = layer.values[k] + args = [(i+1)*w+delta, y(entry.y)] + args[0] += w if trans + if i == @options.windowSize - 1 + @ctx.moveTo.apply @ctx, args + else + @ctx.lineTo.apply @ctx, args + + @ctx.stroke() + + super() diff --git a/debian/missing-sources/epoch/tests/render/basic/area.html b/debian/missing-sources/epoch/tests/render/basic/area.html new file mode 100644 index 0000000..ea23851 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/area.html @@ -0,0 +1,450 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <script src="../js/data.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + </head> + <body> + <h1>Basic Area Chart Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Basic Area</p> + + <ol> + <li><a href="#test-1">Single Series</a></li> + <li><a href="#test-2">Single Series II</a></li> + <li><a href="#test-3">Multi Series</a></li> + <li><a href="#test-4">Multi Series II</a></li> + <li><a href="#test-5">Single Series Transition</a></li> + <li><a href="#test-6">Multi Series Transition</a></li> + <li><a href="#test-7">Single Series to Multi Series Transition</a></li> + <li><a href="#test-8">Layer Color Override</a></li> + <li><a href="#test-9">Categorical Color Switching</a></li> + <li><a href="#test-10">Multi Series without Labels</a></li> + <li><a href="#test-11">Hide/Show Layers</a></li> + <li><a href="#test-12">Data Format</a></li> + </ol> + + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Single Series</h2> + <p>It should display a plot of <code>y = cos(x) + 1</code> over the range <code>[0, 2π)</code>.</p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 64; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x) + 1; + data[0].values.push({x: x, y: y}); + } + + $('#test-1 .epoch').epoch({ type: 'area', data: data }); + }); + </script> + + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Single Series II</h2> + <p>It should display a plot of <code>y = sin(x) + 1</code> over the range <code>[0, 2π)</code>.</p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 64; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.sin(x) + 1; + data[0].values.push({x: x, y: y}); + } + + $('#test-2 .epoch').epoch({ type: 'area', data: data }); + }); + </script> + + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Multi-series Plot</h2> + <p> + It should display a plot of the following functions stacked atop one another: + <ul> + <li><code>y = x</code></li> + <li><code>y = 2x</code></li> + <li><code>y = 3x</code></li> + </ul> + over the range <code>[0, 10)</code>. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function(){ + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 32; + + for (var i = 0; i < length; i++) { + var x = i * 10 / length; + data[0].values.push({x: x, y: x}); + data[1].values.push({x: x, y: 2*x}); + data[2].values.push({x: x, y: 3*x}); + } + + $('#test-3 .epoch').epoch({ type: 'area', data: data }); + }); + </script> + + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Multi-series Plot II</h2> + <p> + It should display a plot of the following functions stacked atop one another: + <ul> + <li><code>y = |x|</code></li> + <li><code>y = x<sup>2</sup></code></li> + <li><code>y = |x<sup>3</sup>|</code></li> + </ul> + over the range [-1, 1). + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 64; + + for (var i = 0; i < length; i++) { + var x = (i - length / 2) / (length / 2); + data[0].values.push({x: x, y: Math.abs(x)}); + data[1].values.push({x: x, y: Math.pow(x, 2)}); + data[2].values.push({x: x, y: Math.abs(Math.pow(x, 3))}); + } + + $('#test-4 .epoch').epoch({ type: 'area', data: data }); + }); + </script> + + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Single Series Transition</h2> + <p> + It should correctly transition between the plots <code>y = |x|</code> over the range [-10, 10) and <code>y = x<sup>2</sup></code> over the range [-20, 20). The transition should be initiated when pressing the buttons below the plot. + </p> + <div class="epoch"></div> + <p> + <button data-index="0">y = x</button> + <button data-index="1">y = x^2</button> + </p> + </div> + + <script> + $(function() { + var data1 = [{label: 'A', values: []}], + data2 = [{label: 'B', values: []}], + data = [data1, data2], + length = 64; + + for (var i = 0; i < length; i++) { + var x1 = (i - length / 2) * 10 / (length / 2), + y1 = Math.abs(x1), + x2 = (i - length / 2) * 20 / (length / 2), + y2 = Math.pow(x2, 2); + data1[0].values.push({x: x1, y: y1}); + data2[0].values.push({x: x2, y: y2}); + } + + var chart = $('#test-5 .epoch').epoch({ + type: 'area', + data: data1 + }); + + $('#test-5 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>6. Multi Series Transition</h2> + <p> + It should correctly render and transition between Set A: + <ul> + <li><code>y = x</code></li> + <li><code>y = 2*x</code></li> + <li><code>y = 3*x</code></li> + </ul> + over the range [1, 100). and Set B: + <ul> + <li><code>y = ln(x)</code></li> + <li><code>y = 2*ln(x)</code></li> + <li><code>y = 3*ln(x)</code></li> + </ul> + over the range [1, 100). The transition should be initiated when pressing the buttons below the plot. + <div class="epoch"></div> + <p> + <button data-index="0">Set A</button> + <button data-index="1">Set B</button> + </p> + </div> + + <script> + $(function() { + var data1 = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ]; + + var data2 = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ]; + + var data = [data1, data2], + length = 128; + + for (var i = 0; i < length; i++) { + var x = (i * 99 / length) + 1; + + data1[0].values.push({x: x, y: x}); + data1[1].values.push({x: x, y: 2*x}); + data1[2].values.push({x: x, y: 3*x}); + + data2[0].values.push({x: x, y: Math.log(x)}); + data2[1].values.push({x: x, y: 2*Math.log(x)}); + data2[2].values.push({x: x, y: 3*Math.log(x)}); + } + + var chart = $('#test-6 .epoch').epoch({ type: 'area', data: data1 }); + + $('#test-6 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + + <!-- Test 7 --> + <div id="test-7" class="test"> + <h2>7. Single Series to Multi Series Transition</h2> + <p> + It should correctly transition between a single series, plotting the functions: + <ul> + <li><code>y = x<sup>2</sup> - 0.5*x</code></li> + </ul> + To a multi series set, plotting the functions: + <ul> + <li><code>y = ln(x)</code></li> + <li><code>y = x</code></li> + <li><code>y = x * ln(x)</code></li> + </ul> + over the range [1, 4) for all plots. The transition should be initiated when pressing the buttons below the plot. + </p> + <div class="epoch"></div> + <p> + <button data-index="0">Single</button> + <button data-index="1">Multi</button> + </p> + </div> + + <script> + $(function() { + var data1 = [{label: 'A', values: []}], + data2 = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + data = [data1, data2], + length = 64; + + for (var i = 0; i < length; i++) { + var x = (3*i/length) + 1; + data1[0].values.push({x: x, y: x*x - 0.5*x}); + data2[0].values.push({x: x, y: Math.log(x)}); + data2[1].values.push({x: x, y: x}); + data2[2].values.push({x: x, y: x * Math.log(x)}); + } + + var chart = $('#test-7 .epoch').epoch({ type: 'area', data: data1 }); + + $('#test-7 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 8 --> + <div id="test-8" class="test"> + <h2>8. Layer Color Override</h2> + <p> + It should display the first layer of the plot as pink, the second layer as green, and the third layer as blue. + </p> + <div class="epoch"></div> + </div> + + <style> + #test-8 .epoch .a .area { fill: pink; } + #test-8 .epoch .b .area { fill: green; } + #test-8 .epoch .c .area { fill: blue; } + </style> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 128; + + for (var i = 0; i < length; i++) { + var x = i * 10 / length + 1, + y = x * Math.log(x); + for (var j = 0; j < data.length; j++) { + data[j].values.push({x: x, y: y}); + } + } + + $('#test-8 .epoch').epoch({ type: 'area', data: data }); + }); + </script> + + <!-- Test 9 --> + <div id="test-9" class="test"> + <h2>9. Categorical Color Switching</h3> + <p> + It should change layer colors automatically when switching between the following categorical color classes on the containing element: + <ul> + <li><code>category10</code></li> + <li><code>category20</code></li> + <li><code>category20b</code></li> + <li><code>category20c</code></li> + </ul> + The colors should change when pressing the buttons for each categorical type below the chart. + </p> + + <div class="epoch category10"></div> + + <p> + <button data-class="category10">category10</button> + <button data-class="category20">category20</button> + <button data-class="category20b">category20b</button> + <button data-class="category20c">category20c</button> + </p> + </div> + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []}, + {label: 'D', values: []}, + {label: 'E', values: []}, + {label: 'F', values: []}, + {label: 'G', values: []} + ], + length = 128, + className = 'category10'; + + for (var i = 0; i < length; i++) { + var x = i * 1 / length; + for (var j = 0; j < data.length; j++) { + data[j].values.push({x: x, y: Math.pow(x, j+1)}); + } + } + + $('#test-9 .epoch').epoch({ type: 'area', data: data }); + + $('#test-9 button').on('click', function(e) { + $('#test-9 .epoch').removeClass(className); + className = $(e.target).attr('data-class'); + $('#test-9 .epoch').addClass(className); + }); + }); + </script> + + <!-- Test 10 --> + <div id="test-10" class="test"> + <h2>10. Multi Series without Labels</h2> + <p> + Correctly render a multi-series plot of: + <ul> + <li><code>y = sin(x) + 1</code></li> + <li><code>y = cos(x) + 1</code></li> + </ul> + where the layers are given without labels. + </p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = [{values: []}, {values: []}], + length = 128; + + for (var i = 0; i <= length; i++) { + var x = i * 4 * Math.PI / length; + data[0].values.push({x: x, y: Math.sin(x) + 1}); + data[1].values.push({x: x, y: Math.cos(x) + 1}); + } + + $('#test-10 .epoch').epoch({ type: 'area', data: data }); + }); + </script> + + <!-- Test 11 --> + <div id="test-11" class="test"> + <h2>11. Hide/Show Layers</h2> + <p>Correctly hide and show multiple layers</p> + <div class="epoch"></div> + <p> + <button data-index="0">Toggle A</button> + <button data-index="1">Toggle B</button> + <button data-index="2">Toggle C</button> + </p> + </div> + <script> + $(function() { + var chart = $('#test-11 .epoch').epoch({ + type: 'area', + data: data().add(function(x) { return x; }) + .add(function(x) { return 1.5*x; }) + .add(function(x) { return 2.0*x; }).get([0, 10], 1) + }); + + $('#test-11 button').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.toggleLayer(index); + }); + }); + </script> + + </body> +</html> diff --git a/debian/missing-sources/epoch/tests/render/basic/bar.html b/debian/missing-sources/epoch/tests/render/basic/bar.html new file mode 100644 index 0000000..07b5122 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/bar.html @@ -0,0 +1,745 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <script src="../js/data.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + </head> + <body> + <h1>Basic Bar Chart Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Basic Bar</p> + + <ol> + <li><a href="#test-1">Single Series</a></li> + <li><a href="#test-2">Single Series II</a></li> + <li><a href="#test-3">Multi Series</a></li> + <li><a href="#test-4">Multi Series II</a></li> + <li><a href="#test-5">Single Series Transition</a></li> + <li><a href="#test-6">Multi Series Transition</a></li> + <li><a href="#test-7">Single Series to Multi Series Transition</a></li> + <li><a href="#test-8">Layer Color Override</a></li> + <li><a href="#test-9">Categorical Color Switching</a></li> + <li><a href="#test-10">Multi Series without Labels</a></li> + <li><a href="#test-11">Horizontally Oriented Single Series</a></li> + <li><a href="#test-12">Horizontally Oriented Multi Series</a></li> + <li><a href="#test-13">Horizontally Oriented Multi Series Transition</a></li> + <li><a href="#test-14">Vertical to Horizontal Transition</a></li> + <li><a href="#test-15">Padding Changes</a></li> + <li><a href="#test-16">Hide/Show Layers</a></li> + <li><a href="#test-17">Data Formatting</a></li> + <li><a href="#test-18">Many bars</a></li> + </ol> + + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Single Series</h2> + <p>Display a plot of <code>y = cos(x) + 1</code> over the range <code>[0, 2π)</code>.</p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 32; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x) + 1; + data[0].values.push({x: x, y: y}); + } + + $('#test-1 .epoch').epoch({ + type: 'bar', + data: data, + tickFormats: { + bottom: function(d) { + return parseFloat(d).toFixed(1); + } + } + }); + }); + </script> + + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Single Series II</h2> + <p>Display a plot of <code>y = sin(x) + 1</code> over the range <code>[0, 2π)</code>.</p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 32; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.sin(x) + 1; + data[0].values.push({x: x, y: y}); + } + + $('#test-2 .epoch').epoch({ + type: 'bar', + data: data, + tickFormats: { + bottom: function(d) { + return parseFloat(d).toFixed(1); + } + } + }); + }); + </script> + + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Multi-series Plot</h2> + <p> + Display a plot of the following functions stacked atop one another: + <ul> + <li><code>y = x</code></li> + <li><code>y = 2x</code></li> + <li><code>y = 3x</code></li> + </ul> + over the range <code>[0, 10)</code>. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function(){ + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 10; + + for (var i = 0; i < length; i++) { + var x = i * 10 / length; + data[0].values.push({x: x, y: x}); + data[1].values.push({x: x, y: 2*x}); + data[2].values.push({x: x, y: 3*x}); + } + + $('#test-3 .epoch').epoch({ + type: 'bar', + data: data + }); + }); + </script> + + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Multi-series Plot II</h2> + <p> + Display a plot of the following functions stacked atop one another: + <ul> + <li><code>y = |x|</code></li> + <li><code>y = x<sup>2</sup></code></li> + <li><code>y = |x<sup>3</sup>|</code></li> + </ul> + over the range [-1, 1). + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 20; + + for (var i = 0; i < length; i++) { + var x = (i - length / 2) / (length / 2); + data[0].values.push({x: x, y: Math.abs(x)}); + data[1].values.push({x: x, y: Math.pow(x, 2)}); + data[2].values.push({x: x, y: Math.abs(Math.pow(x, 3))}); + } + + $('#test-4 .epoch').epoch({ type: 'bar', data: data }); + }); + </script> + + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Single Series Transition</h2> + <p> + Correctly transition between the plots <code>y = |x|</code> over the range [-10, 10) and <code>y = x<sup>2</sup></code> over the range [-20, 20). The transition is initiated by pressing the buttons below the plot. + </p> + <div class="epoch"></div> + <p> + <button data-index="0">y = x</button> + <button data-index="1">y = x^2</button> + </p> + </div> + + <script> + $(function() { + var data1 = [{label: 'A', values: []}], + data2 = [{label: 'B', values: []}], + data = [data1, data2], + length = 40; + + for (var i = 0; i < length; i++) { + var x1 = (i - length / 2) * 10 / (length / 2), + y1 = Math.abs(x1), + x2 = (i - length / 2) * 20 / (length / 2), + y2 = Math.pow(x2, 2); + data1[0].values.push({x: x1, y: y1}); + data2[0].values.push({x: x2, y: y2}); + } + + var chart = $('#test-5 .epoch').epoch({ + type: 'bar', + data: data1 + }); + + $('#test-5 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>6. Multi Series Transition</h2> + <p> + Correctly render and transition between Set A: + <ul> + <li><code>y = x</code></li> + <li><code>y = 2*x</code></li> + <li><code>y = 3*x</code></li> + </ul> + over the range [1, 100). and Set B: + <ul> + <li><code>y = ln(x)</code></li> + <li><code>y = 2*ln(x)</code></li> + <li><code>y = 3*ln(x)</code></li> + </ul> + over the range [1, 100). The transition is initiated by pressing the buttons below the plot. + <div class="epoch"></div> + <p> + <button data-index="0">Set A</button> + <button data-index="1">Set B</button> + </p> + </div> + + <script> + $(function() { + var data1 = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ]; + + var data2 = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ]; + + var data = [data1, data2], + length = 10; + + for (var i = 0; i < length; i++) { + var x = (i * 100 / length) + 1; + + data1[0].values.push({x: x, y: x}); + data1[1].values.push({x: x, y: 2*x}); + data1[2].values.push({x: x, y: 3*x}); + + data2[0].values.push({x: x, y: Math.log(x)}); + data2[1].values.push({x: x, y: 2*Math.log(x)}); + data2[2].values.push({x: x, y: 3*Math.log(x)}); + } + + var chart = $('#test-6 .epoch').epoch({ type: 'bar', data: data1 }); + + $('#test-6 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + + <!-- Test 7 --> + <div id="test-7" class="test"> + <h2>7. Single Series to Multi Series Transition</h2> + <p> + Correctly transition between a single series, plotting the functions: + <ul> + <li><code>y = x<sup>2</sup> - 0.5*x</code></li> + </ul> + To a multi series set, plotting the functions: + <ul> + <li><code>y = ln(x)</code></li> + <li><code>y = x</code></li> + <li><code>y = x * ln(x)</code></li> + </ul> + over the range [1, 4) for all plots. The transition is initiated by pressing the buttons below the plot. + </p> + <div class="epoch"></div> + <p> + <button data-index="0">Single</button> + <button data-index="1">Multi</button> + </p> + </div> + + <script> + $(function() { + var data1 = [{label: 'A', values: []}], + data2 = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + data = [data1, data2], + length = 32; + + for (var i = 0; i < length; i++) { + var x = (3*i/length) + 1; + data1[0].values.push({x: x, y: x*x - 0.5*x}); + data2[0].values.push({x: x, y: Math.log(x)}); + data2[1].values.push({x: x, y: x}); + data2[2].values.push({x: x, y: x * Math.log(x)}); + } + + var chart = $('#test-7 .epoch').epoch({ + type: 'bar', + data: data1, + tickFormats: { + bottom: function(d) { return d.toFixed(1); } + } + }); + + $('#test-7 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 8 --> + <div id="test-8" class="test"> + <h2>8. Layer Color Override</h2> + <p> + Display the first layer of the plot as pink, the second layer as green, and the third layer as blue. + </p> + <div class="epoch"></div> + </div> + + <style> + #test-8 .epoch .bar.a { fill: pink; } + #test-8 .epoch .bar.b { fill: green; } + #test-8 .epoch .bar.c { fill: blue; } + </style> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 10; + + for (var i = 0; i < length; i++) { + var x = i * 10 / length + 1, + y = x * Math.log(x); + for (var j = 0; j < data.length; j++) { + data[j].values.push({x: x, y: y}); + } + } + + $('#test-8 .epoch').epoch({ type: 'bar', data: data }); + }); + </script> + + <!-- Test 9 --> + <div id="test-9" class="test"> + <h2>9. Categorical Color Switching</h3> + <p> + Change layer colors automatically when switching between the following categorical color classes on the containing element: + <ul> + <li><code>category10</code></li> + <li><code>category20</code></li> + <li><code>category20b</code></li> + <li><code>category20c</code></li> + </ul> + Change the categorical colors by pressing the buttons below the chart. + </p> + + <div class="epoch category10"></div> + + <p> + <button data-class="category10">category10</button> + <button data-class="category20">category20</button> + <button data-class="category20b">category20b</button> + <button data-class="category20c">category20c</button> + </p> + </div> + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []}, + {label: 'D', values: []}, + {label: 'E', values: []}, + {label: 'F', values: []}, + {label: 'G', values: []} + ], + length = 4, + className = 'category10'; + + for (var i = 0; i < length; i++) { + var x = i * 1 / length; + for (var j = 0; j < data.length; j++) { + data[j].values.push({x: x, y: Math.pow(x, j+1)}); + } + } + + $('#test-9 .epoch').epoch({ + type: 'bar', + data: data, + tickFormats: { + bottom: function(d) { return d.toFixed(1); } + } + }); + + $('#test-9 button').on('click', function(e) { + $('#test-9 .epoch').removeClass(className); + className = $(e.target).attr('data-class'); + $('#test-9 .epoch').addClass(className); + }); + }); + </script> + + <!-- Test 10 --> + <div id="test-10" class="test"> + <h2>10. Multi Series without Labels</h2> + <p> + Correctly render a multi-series plot of: + <ul> + <li><code>y = sin(x) + 1</code></li> + <li><code>y = cos(x) + 1</code></li> + </ul> + where the layers are given without labels. + </p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = [{values: []}, {values: []}], + length = 32; + + for (var i = 0; i <= length; i++) { + var x = i * 4 * Math.PI / length; + data[0].values.push({x: x, y: Math.sin(x) + 1}); + data[1].values.push({x: x, y: Math.cos(x) + 1}); + } + + $('#test-10 .epoch').epoch({ + type: 'bar', + data: data, + tickFormats: { + bottom: function(d) { return d.toFixed(1); } + } + }); + }); + </script> + + <!-- Test 11 --> + <div id="test-11" class="test"> + <h2>11. Horizontally Oriented Single Series</h2> + <p> + Correctly render the single series plot of: + <ul> + <li>A - 20</li> + <li>B - 30</li> + <li>C - 60</li> + </ul> + using a horizontal orientation. + </p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = [{values: [ + {x: 'A', y: 20}, + {x: 'B', y: 30}, + {x: 'C', y: 60} + ]}]; + + $('#test-11 .epoch').epoch({ + type: 'bar', + orientation: 'horizontal', + data: data + }); + }); + </script> + + <!-- Test 12 --> + <div id="test-12" class="test"> + <h2>12. Horizontally Oriented Multi Series</h2> + <p> + Correctly render the multi series plot of: + <ul> + <li>A - 10, 30</li> + <li>B - 20, 50</li> + <li>C - 60, 10</li> + </ul> + using a horizontal orientation. + </p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = [ + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 60}] }, + { values: [{x: 'A', y: 30}, {x: 'B', y: 50}, {x: 'C', y: 10}] } + ]; + + $('#test-12 .epoch').epoch({ + type: 'bar', + orientation: 'horizontal', + data: data + }); + }); + </script> + + <!-- Test 13 --> + <div id="test-13" class="test"> + <h2>13. Horizontally Oriented Multi Series Transition</h2> + <p> + Correctly render the Horizontally oriented multi series plot of: + <ul> + <li>A - 10, 10</li> + <li>B - 20, 20</li> + <li>C - 30, 30</li> + </ul> + and transition to the single series plot: + <ul> + <li>A - 5</li> + <li>B - 10</li> + <li>C - 40</li> + </ul> + </p> + <div class="epoch"></div> + <p> + <button data-index="0">Multi</button> + <button data-index="1">Single</button> + </p> + </div> + <script> + $(function() { + var data1 = [ + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 30}] }, + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 30}] } + ], + data2 = [ + { values: [{x: 'A', y: 5}, {x: 'B', y: 10}, {x: 'C', y: 40}] } + ], + data = [data1, data2]; + + var chart = $('#test-13 .epoch').epoch({ + type: 'bar', + orientation: 'horizontal', + data: data1 + }); + + $('#test-13 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <div id="test-14" class="test"> + <h2>14. Vertical to Horizontal Transition</h2> + <div class="epoch"></div> + <p> + <button data-orientation="vertical">Vertical</button> + <button data-orientation="horizontal">Horizontal</button> + </p> + </div> + <script> + $(function() { + var data = [ + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 30}] }, + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 30}] } + ]; + + var chart = $('#test-14 .epoch').epoch({ + type: 'bar', + data: data + }); + + $('#test-14 button').click(function(e) { + var orientation = $(e.target).attr('data-orientation'); + chart.option('orientation', orientation); + }); + }); + </script> + + <div id="test-15" class="test"> + <h2>15. Padding Changes</h2> + <div class="epoch"></div> + <p> + <button data-index="0">Regular Padding</button> + <button data-index="1">Fat Padding</button> + </p> + </div> + <script> + $(function() { + var data = [ + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 30}] }, + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 30}] } + ]; + + var padding = [ + { + padding: { bar: 0.08, group: 0.1 }, + outerPadding: { bar: 0.08, group: 0.1 } + }, + { + padding: { bar: 0.2, group: 0.3 }, + outerPadding: { bar: 0.1, group: 0.2 } + } + ]; + + var chart = $('#test-15 .epoch').epoch({ type: 'bar', data: data }); + $('#test-15 button').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.option('padding', padding[index].padding); + chart.option('outerPadding', padding[index].outerPadding); + }); + }); + </script> + + <div id="test-16" class="test"> + <h2>16. Hide/Show Layers</h2> + <div class="epoch"></div> + <p> + <button class="toggle" data-index="0">Toggle A</button> + <button class="toggle" data-index="1">Toggle B</button> + <button class="toggle" data-index="2">Toggle C</button> + | + <button class="orientation">Orientation</button> + </p> + </div> + <script> + $(function() { + var chart = $('#test-16 .epoch').epoch({ + type: 'bar', + data: data().add(function(x) { return x; }) + .add(function(x) { return 1.5*x; }) + .add(function(x) { return 2.0*x; }).get([0, 10], 1) + }); + + $('#test-16 button.toggle').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.toggleLayer(index); + }); + + $('#test-16 button.orientation').click(function(e) { + if (chart.option('orientation') == 'vertical') + chart.option('orientation', 'horizontal') + else + chart.option('orientation', 'vertical') + }) + }); + </script> + + <div id="test-17" class="test"> + <h2>17. Data Formatting</h2> + <p>Ensure the chart works with the array, tuple, and key-value data formats.<p> + <div class="epoch array"></div> + <div class="epoch tuple"></div> + <div class="epoch keyvalue"></div> + </div> + <script> + $(function() { + $('#test-17 .array').epoch({ + type: 'bar', + data: [1, 2, 4, 8, 16, 32], + dataFormat: 'array' + }); + + $('#test-17 .tuple').epoch({ + type: 'bar', + dataFormat: 'tuple', + data: [ + [[0, 1], [1, 2], [2, 4], [3, 8]], + [[0, 1], [1, 3], [2, 9], [3, 27]] + ] + }); + + $('#test-17 .keyvalue').epoch({ + type: 'bar', + dataFormat: { + name: 'keyvalue', + arguments: [['a', 'b']], + options: { + x: function(d, i) { return d.x; } + } + }, + data: [ + {x: 1, a: 1, b: 1 }, + {x: 2, a: 2, b: 4 }, + {x: 3, a: 3, b: 6 }, + {x: 4, a: 4, b: 8 }, + {x: 5, a: 5, b: 10 } + ] + }); + }); + </script> + + <div id="test-18" class="test"> + <h2>18. Bar Ticks</h2> + <p>Ensure that we can use ticks option with bar charts.<p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = [], + size = 100; + + for (var i = 0; i < size; i++) { + data.push({ + x: i + 1, + a: 1 + Math.random()*2*i, + b: 1 + Math.random()*2*i + }); + } + + var chart = $('#test-18 .epoch').epoch({ + type: 'bar', + data: data, + dataFormat: { + name: 'keyvalue', + arguments: [['a', 'b']], + options: { + x: function(d, i) { return d.x; } + } + }, + }); + + console.log(chart.data); + }); + </script> + </body> +</html> diff --git a/debian/missing-sources/epoch/tests/render/basic/histogram.html b/debian/missing-sources/epoch/tests/render/basic/histogram.html new file mode 100644 index 0000000..505054e --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/histogram.html @@ -0,0 +1,155 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <script src="../js/data.js"></script> + + <script> + window.beta = function(a, b) { + a = a ? a : 2; + b = b ? b : 5; + + var histogram = new BetaData(a, b, 1).next()[0].histogram, + values = []; + + for(var x in histogram) + values.push({x: x, y: histogram[x]}) + + return [{values: values}]; + } + </script> + + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + </head> + <body> + <h1>Basic Bar Chart Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Basic Bar</p> + + <ol> + <li><a href="#test-1">Beta(2, 5)</a></li> + <li><a href="#test-2">Beta(2, 5) Horizontal</a></li> + <li><a href="#test-3">Option: buckets</a></li> + <li><a href="#test-4">Options: bucketRange & cutOutliers</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Beta(2, 5)</h2> + <p>Plot a random selection of points from the Beta(2, 5) distribution as a histogram.</p> + <div class="epoch"></div> + <p><button>Refresh</button></p> + </div> + <script> + $(function() { + var chart = $('#test-1 .epoch').epoch({ + type: 'histogram', + buckets: 20, + data: beta(2, 5) + }); + + $('#test-1 button').click(function(e) { + chart.update( beta(2, 5) ); + }); + }); + </script> + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Beta(2, 5) Horizontal</h2> + <p>Plot a random selection of points from Beta(2, 5) and display in a horizontal histogram.</p> + <div class="epoch"></div> + <p><button>Refresh</button></p> + </div> + <script> + $(function() { + var chart = $('#test-2 .epoch').epoch({ + type: 'histogram', + buckets: 10, + data: beta(2, 5), + orientation: 'horizontal' + }); + + $('#test-2 button').click(function(e) { + chart.update( beta(2, 5) ); + }); + }); + </script> + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Option: buckets</h2> + <p>Plot Beta(2, 2) and change number of buckets on the fly.</p> + <div class="epoch"></div> + <p> + <button data-value="5">5 Buckets</button> + <button data-value="10">10 Buckets</button> + <button data-value="20">20 Buckets</button> + <button data-value="25">25 Buckets</button> + <button data-value="50">50 Buckets</button> | + <button class="refresh">Refresh</button> + </p> + </div> + <script> + $(function() { + var chart = $('#test-3 .epoch').epoch({ + type: 'histogram', + buckets: 10, + data: beta(2, 2) + }); + + $('#test-3 button[data-value]').click(function(e) { + var buckets = parseInt($(e.target).attr('data-value')); + chart.option('buckets', buckets); + }); + + $('#test-3 button.refresh').click(function(e) { + chart.update( beta(2, 2) ); + }); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Options: bucketRange & cutOutliers</h2> + <p>Plot beta(2, 5) and change the bucket range on the fly.</p> + <div class="epoch"></div> + <p> + <button data-range="0,100">Range [0, 100]</button> + <button data-range="0,50">Range [0, 50]</button> + <button data-range="25,75">Range [25, 75]</button> | + <button class="cutOutliers">Cut Outliers</button> + <button class="keepOutliers">Keep Outliers</button> | + <button class="refresh">Refresh</button> + </p> + </div> + <script> + $(function() { + var chart = $('#test-4 .epoch').epoch({ + type: 'histogram', + buckets: 25, + data: beta(2, 5) + }); + + $('#test-4 button[data-range]').click(function(e) { + var range = $(e.target).attr('data-range').split(',').map(function(d) { return +d; }); + chart.option('bucketRange', range); + }); + + $('#test-4 .cutOutliers').click(function(e) { + chart.option('cutOutliers', true); + }); + + $('#test-4 .keepOutliers').click(function(e) { + chart.option('cutOutliers', false); + }); + + $('#test-4 button.refresh').click(function(e) { + chart.update( beta(2, 5) ); + }); + }); + </script> + </body> +</html> diff --git a/debian/missing-sources/epoch/tests/render/basic/line.html b/debian/missing-sources/epoch/tests/render/basic/line.html new file mode 100644 index 0000000..37de38d --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/line.html @@ -0,0 +1,471 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <script src="../js/data.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + </head> + <body> + <h1>Basic Line Chart Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Basic Line</p> + + <ol> + <li><a href="#test-1">Single Series</a></li> + <li><a href="#test-2">Single Series II</a></li> + <li><a href="#test-3">Multi Series</a></li> + <li><a href="#test-4">Single Series Transition</a></li> + <li><a href="#test-5">Multi Series Transition</a></li> + <li><a href="#test-6">Single to Multi Series Transition</a></li> + <li><a href="#test-7">Color Override</a></li> + <li><a href="#test-8">Categorical Colors</a></li> + <li><a href="#test-9">Multi Series without Labels</a></li> + <li><a href="#test-10">Multi Series with Fixed Domain</a></li> + <li><a href="#test-11">Show/Hide Layers</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Single Series</h2> + <p>Display a plot of <code>y = cos(x)</code> over the range <code>[-2π, 2π)</code>.</p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 128; + for (var i = 0; i < length; i++) { + var x = i * 4 * Math.PI / length - 2 * Math.PI, + y = Math.cos(x); + data[0].values.push({x: x, y: y}); + } + $('#test-1 .epoch').epoch({ type: 'line', data: data }); + }); + </script> + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Single Series II</h2> + <p>Display a plot of <code>y = e<sup>x</sup>*sin(x)</code> from <code>[0, &pi)</code>.</p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 128; + for (var i = 0; i < length; i++) { + var x = i * Math.PI / length, + y = Math.exp(x) * Math.sin(x); + data[0].values.push({x: x, y: y}); + } + $('#test-2 .epoch').epoch({type: 'line', data: data}); + }) + </script> + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Multi Series</h2> + <p> + Display a plot of the following functions over the range [0, 2π]: + <ul> + <li><code>x*sin(x)</code></li> + <li><code>x*cos(x)</code></li> + </ul> + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [ + { label: 'A', values: [] }, + { label: 'B', values: [] } + ], + length = 128; + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length; + data[0].values.push({x: x, y: x*Math.sin(x)}); + data[1].values.push({x: x, y: x*Math.cos(x)}); + } + $('#test-3 .epoch').epoch({type: 'line', data: data}); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Single Series Transition</h2> + <p> + Correctly transition between the functions <code>y = 1 / x</code> and <code>y = x<sup>2</sup></code> over the range [1, 2). + Use the buttons below the chart to initiate the transitions. + </p> + <div class="epoch"></div> + <p> + <button data-index="0">y = 1 / x</button> + <button data-index="1">y = x^2</button> + </p> + </div> + + <script> + $(function() { + var data1 = [{label: 'A', values: []}], + data2 = [{label: 'B', values: []}], + data = [data1, data2], + length = 128; + + for (var i = 0; i < length; i++) { + var x = i / length + 1; + data1[0].values.push({x: x, y: 1/x}); + data2[0].values.push({x: x, y: x*x}); + } + + var chart = $('#test-4 .epoch').epoch({ type: 'line', data: data1 }); + + $('#test-4 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Multi Series Transition</h2> + <p> + Correctly transition between Set A: + <ul> + <li><code>y = sin(x)</code></li> + <li><code>y = x - x<sup>3</sup>/3! + x<sup>5</sup>/5!</code></li> + </ul> + Set B: + <ul> + <li><code>y = cos(x)</code></li> + <li><code>y = 1 - x<sup>2</sup>/2! + x<sup>4</sup>/4!</code></li> + </ul> + and Set C: + <ul> + <li><code>y = sin(x) - (x - x<sup>3</sup>/3! + x<sup>5</sup>/5!)</code></li> + <li><code>y = cos(x) - (1 - x<sup>2</sup>/2! + x<sup>4</sup>/4!)</code></li> + </ul> + </p> + <div class="epoch"></div> + <p> + <button data-index="0">Set A</button> + <button data-index="1">Set B</button> + <button data-index="2">Set C</button> + </p> + </div> + + <script> + $(function() { + var fac = function(n) { + var result = n; + while(--n > 1) { + result *= n; + } + return result; + }; + + var taylorSin = function(x) { + return x - Math.pow(x, 3) / fac(3) + Math.pow(x, 5) / fac(5); + }; + + var taylorCos = function(x) { + return 1 - Math.pow(x, 2) / fac(2) + Math.pow(x, 4) / fac(4); + }; + + var data1 = [ + { label: 'A', values: [] }, + { label: 'B', values: [] } + ], + data2 = [ + { label: 'A', values: [] }, + { label: 'B', values: [] } + ], + data3 = [ + { label: 'A', values: [] }, + { label: 'B', values: [] } + ], + data = [data1, data2, data3], + length = 64; + + for (var i = 0; i <= length; i++) { + var x = i * 8 / length - 4; + data1[0].values.push({x: x, y: Math.sin(x)}); + data1[1].values.push({x: x, y: taylorSin(x)}); + data2[0].values.push({x: x, y: Math.cos(x)}); + data2[1].values.push({x: x, y: taylorCos(x)}); + data3[0].values.push({x: x, y: Math.sin(x) - taylorSin(x)}); + data3[1].values.push({x: x, y: Math.cos(x) - taylorCos(x)}); + } + + var chart = $('#test-5 .epoch').epoch({ type: 'line', data: data1 }); + + $('#test-5 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>6. Single to Multi Series Transition</h2> + <p> + Correctly transition between Set A: + <ul> + <li><code>y = ln(x)</code></li> + </ul> + Set B: + <ul> + <li><code>y = ln(x)</code></li> + <li><code>y = x * ln(x)</code></li> + <li><code>y = x * ln(x)<sup>2</sup></code></li> + </ul> + </p> + <div class="epoch"></div> + <p> + <button data-index="0">Single</button> + <button data-index="1">Multi</button> + </p> + </div> + + <script> + $(function() { + var data1 = [{label: 'A', values: []}], + data2 = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + data = [data1, data2], + length = 128; + + for (var i = 0; i <= length; i++) { + var x = i * 2 / length + 1; + data1[0].values.push({x: x, y: Math.log(x)}); + data2[0].values.push({x: x, y: Math.log(x)}); + data2[1].values.push({x: x, y: x * Math.log(x)}); + data2[2].values.push({x: x, y: x * Math.log(x) * Math.log(x)}); + } + + var chart = $('#test-6 .epoch').epoch({ type: 'line', data: data1 }); + + $('#test-6 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 7 --> + <div id="test-7" class="test"> + <h2>7. Color Override</h2> + <p> + Display the first layer of the plot as pink, the second layer as green, and the third layer as blue. + </p> + <div class="epoch"></div> + </div> + + <style> + #test-7 .epoch .a .line { stroke: pink; } + #test-7 .epoch .b .line { stroke: green; } + #test-7 .epoch .c .line { stroke: blue; } + </style> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 64; + + for (var i = 0; i < length; i++) { + var x = i * 1 / length + 0.5; + for (var j = 0; j < data.length; j++) { + data[j].values.push({x: x, y: Math.pow(x, 2*j) * Math.log(x)}); + } + } + + $('#test-7 .epoch').epoch({ type: 'line', data: data }); + }); + </script> + + <!-- Test 8 --> + <div id="test-8" class="test"> + <h2>Categorical Colors</h2> + <p> + Change layer colors automatically when switching between the following categorical color classes on the containing element: + <ul> + <li><code>category10</code></li> + <li><code>category20</code></li> + <li><code>category20b</code></li> + <li><code>category20c</code></li> + </ul> + Change the categorical colors by pressing the buttons below the chart. + </p> + <div class="epoch category10"></div> + <p> + <button data-class="category10">category10</button> + <button data-class="category20">category20</button> + <button data-class="category20b">category20b</button> + <button data-class="category20c">category20c</button> + </p> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []}, + {label: 'D', values: []}, + {label: 'E', values: []}, + {label: 'F', values: []}, + {label: 'G', values: []} + ], + length = 64, + className = 'category10'; + + for (var i = 0; i <= length; i++) { + var x = i * 1 / length; + for (var j = 0; j < data.length; j++) { + data[j].values.push({x: x, y: Math.pow(x, j+1)}); + } + } + + $('#test-8 .epoch').epoch({ type: 'line', data: data }); + + $('#test-8 button').on('click', function(e) { + $('#test-8 .epoch').removeClass(className); + className = $(e.target).attr('data-class'); + $('#test-8 .epoch').addClass(className); + }); + }); + </script> + + + <!-- Test 9 --> + <div id="test-9" class="test"> + <h2>9. Multi Series without Labels</h2> + <p> + Correctly render a multi-series plot of: + <ul> + <li><code>y = sin(x)</code></li> + <li><code>y = cos(x)</code></li> + </ul> + where the layers are given without labels. + </p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = [{values: []}, {values: []}], + length = 128; + + for (var i = 0; i <= length; i++) { + var x = i * 4 * Math.PI / length; + data[0].values.push({x: x, y: Math.sin(x)}); + data[1].values.push({x: x, y: Math.cos(x)}); + } + + $('#test-9 .epoch').epoch({ type: 'line', data: data }); + }); + </script> + + <!-- Test 10 --> + <div id="test-10" class="test"> + <h2>10. Multi Series with Fixed Domain</h2> + <p> + Display a plot of the following functions: + <ul> + <li><code>x*sin(x)</code></li> + <li><code>x*cos(x)</code></li> + </ul> + On the domain <code>[0, 5]</code> and range <code>[0, 4]</code>. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [ + { label: 'A', values: [] }, + { label: 'B', values: [] } + ], + length = 128; + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length; + data[0].values.push({x: x, y: x*Math.sin(x)}); + data[1].values.push({x: x, y: x*Math.cos(x)}); + } + $('#test-10 .epoch').epoch({ + type: 'line', + data: data, + domain: [0, 5], + range: [0, 4] + }); + }); + </script> + + <!-- Test 11 --> + <div id="test-11" class="test"> + <h2>11. Show/Hide Layers</h2> + <div class="epoch"></div> + <p> + <button data-index="0">Toggle A</button> + <button data-index="1">Toggle B</button> + <button data-index="2">Toggle C</button> + </p> + </div> + <script> + $(function() { + var chart = $('#test-11 .epoch').epoch({ + type: 'line', + data: data().add(function(x) { return x; }) + .add(function(x) { return 1.5*x; }) + .add(function(x) { return 2.0*x; }).get([0, 10], 1) + }); + + $('#test-11 button').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.toggleLayer(index); + }); + }); + </script> + + <!-- Test 12 --> + <div id="test-12" class="test"> + <h2>12. Multi-axis</h2> + <div class="epoch"></div> + </div> + <script> + $(function() { + var d = [ + { range: 'left', values: [] }, + { range: 'right', values: [] } + ]; + + for (var i = 0; i < 40; i++) { + d[0].values.push({ x: i, y: 10*i }); + d[1].values.push({ x: i, y: Math.cos(2*Math.PI*i/40.0) }); + } + + var chart = $('#test-12 .epoch').epoch({ + type: 'line', + data: d, + axes: ['left', 'right'], + range: { + left: 'left', + right: 'right' + } + }); + }); + </script> + </body> +</html> diff --git a/debian/missing-sources/epoch/tests/render/basic/model.html b/debian/missing-sources/epoch/tests/render/basic/model.html new file mode 100644 index 0000000..2814fd8 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/model.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <script src="../js/data.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + <style> + body { background: #333; color: #d0d0d0; } + a:link, a:visited { color: white; color: white; } + + .epoch { + height: 220px; + } + + #sparkline { height: 50px; } + + </style> + </head> + <body class="epoch-theme-dark"> + <h1>Basic Chart Model / Data Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Basic Chart Model / Data Test</p> + + <p> + <button class="set" data-index="0">Data A</button> + <button class="set" data-index="1">Data B</button> + <button class="set" data-index="2">Data C</button> + </p> + + <div id="sparkline" class="epoch"></div> + <div id="area" class="epoch"></div> + <div id="bar" class="epoch"></div> + + <script> + $(function() { + var dataA = []; + for (var j = 0; j < 3; j++) { + var layer = []; + for (var i = 0; i < 20; i++) { + layer.push(50 + Math.random()*20); + } + dataA.push(layer); + } + + var dataB = []; + for (var j = 0; j < 2; j++) { + var layer = []; + for (i = 0; i < 30; i++) { + layer.push(10 + Math.random() * 40) + } + dataB.push(layer); + } + + var dataC = []; + for (var i = 0; i < 50; i++) { + dataC.push(Math.random() * 100); + } + + var data = [dataA, dataB, dataC]; + + // Setup the model + window.model = new Epoch.Model({ dataFormat: 'array' }); + model.setData(dataA); + + // Make the charts and associate them with the model + var sparkline = $('#sparkline').epoch({ + type: 'line', + axes: ['left', 'right'], + model: model + }); + + var area = $('#area').epoch({ + type: 'area', + axes: ['left', 'right', 'bottom'], + model: model + }); + + window.bar = $('#bar').epoch({ + type: 'bar', + axes: ['left', 'right', 'bottom'], + model: model + }); + + // Click listeners for the buttons + $('button.set').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + model.setData(data[index]); + }); + }) + </script> + </body> +</html>
\ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/basic/options.html b/debian/missing-sources/epoch/tests/render/basic/options.html new file mode 100644 index 0000000..5ff2d7e --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/options.html @@ -0,0 +1,267 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <script src="../js/data.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + <style> + body { background: #333; color: #d0d0d0; } + a:link, a:visited { color: white; color: white; } + </style> + </head> + <body class="epoch-theme-dark"> + <h1>Basic Chart Options and Events</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Basic Chart Options and Events</p> + <ol> + <li><a href="#test-1">Axes</a></li> + <li><a href="#test-2">Margins</a></li> + <li><a href="#test-3">Ticks and Tick Formats</a></li> + <li><a href="#test-4">Resize</a></li> + <li><a href="#test-5">Domain</a></li> + <li><a href="#test-6">Range</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Axes</h2> + <p> + Correctly add and remove axes when options are set. + </p> + <div class="epoch"></div> + <div class="controls"> + <button data-index="0">All</button> + <button data-index="1">[left, right]</button> + <button data-index="2">[top, bottom]</button> + <button data-index="3">[left, bottom]</button> + <button data-index="4">[top, right]</button> + <button data-index="5">None</button> + </div> + </div> + <script> + $(function() { + var axes = [ + ['top', 'left', 'bottom', 'right'], + ['left', 'right'], + ['top', 'bottom'], + ['left', 'bottom'], + ['top', 'right'], + [] + ]; + + var chart = $('#test-1 .epoch').epoch({ + type: 'line', + data: data().add(function(x) { return Math.cos(x) }).get([0, 2*Math.PI], Math.PI/25) + }); + + $('#test-1 button').click(function(e) { + chart.option('axes', axes[parseInt($(e.target).attr('data-index'))]); + }); + }); + </script> + + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Margins</h2> + <p> + Correctly resize margins when options are set. + </p> + <div class="epoch"></div> + <div class="controls"> + <button data-index="0">No Margins</button> + <button data-index="1">Big Margins</button> + <button data-index="2">Small Margins</button> + </div> + </div> + <script> + $(function() { + var sizes = [ + {margins: {top: null, left: null, bottom: null, right: null }}, + {margins: {top: 60, left: 60, bottom: 60, right: 60 }}, + {margins: {top: 20, left: 20, bottom: 20, right: 20 }} + ]; + + var chart = $('#test-2 .epoch').epoch({ + type: 'line', + data: data().add(function(x) { return Math.sin(x); }) + .add(function(x) { return Math.cos(x); }) + .get([0, 2*Math.PI], Math.PI/25), + axes: [] + }); + + $('#test-2 button').click(function(e) { + var margins = sizes[parseInt($(e.target).attr('data-index'))]; + console.log(margins); + chart.option(margins); + }); + }); + </script> + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Ticks and Tick Formats</h2> + <p> + Correctly resize margins when options are set. + </p> + <div class="epoch"></div> + <div class="controls"> + <button class="ticks">Toggle Ticks</button> + <button class="tickFormats">Toggle Tick Formats</button> + </div> + </div> + <script> + $(function() { + var ticksIndex = 0, ticks = [ + { + ticks: { + top: 14, + bottom: 14, + left: 5, + right: 5, + } + }, + { + ticks: { + top: 30, + left: 2, + bottom: 30, + right: 2 + } + } + ]; + + var tickFormatsIndex = 0, tickFormats = [ + { + tickFormats: { + top: Epoch.Formats.regular, + bottom: Epoch.Formats.regular, + left: Epoch.Formats.si, + right: Epoch.Formats.si + } + }, + { + tickFormats: { + top: function(d) { return parseInt(100*d)+'%' }, + bottom: function(d) { return parseInt(100*d)+'%' }, + left: function(d) { return parseInt(100*d)+'%' }, + right: function(d) { return parseInt(100*d)+'%' } + } + } + ]; + + var chart = $('#test-3 .epoch').epoch({ + type: 'area', + data: data().add(function(x) { return (Math.sin(x)+1) / (x+1); }) + .add(function(x) { return (Math.cos(x)+1) / (x+2); }) + .get([0, 2*Math.PI], Math.PI/25), + axes: ['top', 'left', 'right', 'bottom'] + }); + + $('#test-3 button.ticks').click(function(e) { + ticksIndex = (ticksIndex + 1) % ticks.length; + chart.option(ticks[ticksIndex]); + }); + + $('#test-3 button.tickFormats').click(function(e) { + tickFormatsIndex = (tickFormatsIndex + 1) % tickFormats.length; + chart.option(tickFormats[tickFormatsIndex]); + }); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Resize</h2> + <p> + Correctly resize the chart when the width and height options are set. + </p> + <div class="epoch" style="width: 600px; height: 200px"></div> + <div class="controls"> + <button data-index="0">Original</button> + <button data-index="1">Bigger</button> + </div> + </div> + <script> + $(function() { + var sizes = [ + [600, 200], + [800, 320] + ]; + + var chart = $('#test-4 .epoch').epoch({ + type: 'line', + data: data().add(function(x) { return Math.sin(x) }).get([0, 2*Math.PI], Math.PI/25) + }); + + $('#test-4 button').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.option('width', sizes[index][0]); + chart.option('height', sizes[index][1]); + }); + }); + </script> + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>6. Option: domain</h2> + <div class="epoch"></div> + <p> + <button data-index="0">Domain From Data Extent</button> + <button data-index="1">[0, π]</button> + </p> + </div> + <script> + $(function() { + var domains = [ + null, + [0, Math.PI] + ]; + + var chart = $('#test-5 .epoch').epoch({ + type: 'line', + data: data().add(function(x) { return 1 - Math.sin(x) }).get([0, 2*Math.PI], Math.PI/25) + }); + + $('#test-5 button').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.option('domain', domains[index]) + }); + }); + </script> + + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>7. Option: range</h2> + <div class="epoch"></div> + <p> + <button data-index="0">Range From Data Extent</button> + <button data-index="1">[0, 1.5]</button> + </p> + </div> + <script> + $(function() { + var ranges = [ + null, + [0, 1.5] + ]; + + var chart = $('#test-6 .epoch').epoch({ + type: 'area', + data: data().add(function(x) { return 1 - Math.cos(x) }).get([0, 2*Math.PI], Math.PI/25) + }); + + $('#test-6 button').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + console.log(ranges[index]); + chart.option('range', ranges[index]) + + }); + }); + </script> + + </body> +</html>
\ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/basic/pie.html b/debian/missing-sources/epoch/tests/render/basic/pie.html new file mode 100644 index 0000000..fe105e8 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/pie.html @@ -0,0 +1,346 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + <style> + .test .epoch { + width: 350px; + height: 350px; + } + </style> + </head> + <body> + <h1>Basic Pie Chart Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Basic Pie</p> + <ol> + <li><a href="#test-1">Basic Pie Test</a></li> + <li><a href="#test-2">Basic Donut Test</a></li> + <li><a href="#test-3">Pie Tranisition I</a></li> + <li><a href="#test-4">Pie Tranisition II</a></li> + <li><a href="#test-5">Color Override</a></li> + <li><a href="#test-6">Categorical Colors</a></li> + <li><a href="#test-7">Pie Chart Layers without Labels</a></li> + <li><a href="#test-8">Margin Changes</a></li> + <li><a href="#test-9">Inner Changes</a></li> + <li><a href="#test-10">Show/Hide Layers</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>Basic Pie Test</h2> + <p> + Correctly render a pie chart with three categories: + <ul> + <li>A - 20</li> + <li>B - 45</li> + <li>C - 35</li> + </ul> + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + $('#test-1 .epoch').epoch({ + type: 'pie', + data: [ + {label: 'A', value: 20}, + {label: 'B', value: 45}, + {label: 'C', value: 35} + ] + }); + }); + </script> + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Basic Donut Test</h2> + <p> + Correctly render a donut chart with three categories: + <ul> + <li>A - 50</li> + <li>B - 30</li> + <li>C - 20</li> + </ul> + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + $('#test-2 .epoch').epoch({ + type: 'pie', + inner: 100, + data: [ + {label: 'A', value: 50}, + {label: 'B', value: 30}, + {label: 'C', value: 20} + ] + }); + }); + </script> + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Pie Tranisition I</h2> + <p> + Correctly transition between set A: + <ul> + <li>A - 20</li> + <li>B - 80</li> + </ul> + and set B: + <ul> + <li>A - 20</li> + <li>B - 30</li> + <li>C - 50</li> + </ul> + Use the buttons below the chart to initiate the transitions. + </p> + <div class="epoch"></div> + <p> + <button data-index="0">Set A</button> + <button data-index="1">Set B</button> + </p> + </div> + + <script> + $(function() { + var data1 = [ + {label: 'A', value: 20}, + {label: 'B', value: 80} + ], + data2 = [ + {label: 'A', value: 20}, + {label: 'B', value: 30}, + {label: 'C', value: 50} + ], + data = [data1, data2], + chart = $('#test-3 .epoch').epoch({ type: 'pie', data: data1 }); + + $('#test-3 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Pie Tranisition II</h2> + <p> + Correctly transition between set A: + <ul> + <li>A - 20</li> + <li>B - 80</li> + </ul> + and set B: + <ul> + <li>A - 50</li> + <li>B - 50</li> + </ul> + Use the buttons below the chart to initiate the transitions. + </p> + <div class="epoch"></div> + <p> + <button data-index="0">Set A</button> + <button data-index="1">Set B</button> + </p> + </div> + + <script> + $(function() { + var data1 = [ + {label: 'A', value: 20}, + {label: 'B', value: 80} + ], + data2 = [ + {label: 'A', value: 50}, + {label: 'B', value: 50} + ], + data = [data1, data2], + chart = $('#test-4 .epoch').epoch({ type: 'pie', data: data1 }); + + $('#test-4 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Color Override</h2> + <p> + Override the colors as such: + <ul> + <li>A - Pink</li> + <li>B - Green</li> + <li>C - Blue</li> + </ul> + </p> + <div class="epoch"></div> + </div> + + <style> + #test-5 .arc.a path { fill: pink; } + #test-5 .arc.b path { fill: green; } + #test-5 .arc.c path { fill: blue; } + </style> + + <script> + $(function() { + $('#test-5 .epoch').epoch({ + type: 'pie', + data: [ + {label: 'A', value: 20}, + {label: 'B', value: 45}, + {label: 'C', value: 35} + ] + }); + }); + </script> + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>6. Categorical Colors</h2> + <p> + Correctly transition between different categorical colors sets. + </p> + <div class="epoch category10"></div> + <p> + <button data-class="category10">category10</button> + <button data-class="category20">category20</button> + <button data-class="category20b">category20b</button> + <button data-class="category20c">category20c</button> + </p> + </div> + + + <script> + $(function() { + var data = [], + className = 'category10'; + + for (var j = 0; j < 10; j++) { + data.push({label: String.fromCharCode(65+j), value: 10}); + } + + $('#test-6 .epoch').epoch({ + type: 'pie', + inner: 100, + data: data + }); + + $('#test-6 button').on('click', function(e) { + $('#test-6 .epoch').removeClass(className); + className = $(e.target).attr('data-class'); + $('#test-6 .epoch').addClass(className); + }); + }); + </script> + + + <!-- Test 7 --> + <div id="test-7" class="test"> + <h2>7. Pie Chart Layers without Labels</h2> + <p> + Correctly render a pie chart with three categories: + <ul> + <li>30</li> + <li>35</li> + <li>35</li> + </ul> + when the layers are not provided labels. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + $('#test-7 .epoch').epoch({ + type: 'pie', + data: [ {value: 30}, {value: 35}, {value: 35} ] + }); + }); + </script> + + <!-- Test 8 --> + <div id="test-8" class="test"> + <h2>8. Margin Changes</h2> + <div class="epoch"></div> + <p> + <button data-index="0">Small</button> + <button data-index="1">Big</button> + </p> + </div> + <script> + $(function() { + var margins = [10, 40]; + var chart = $('#test-8 .epoch').epoch({ + type: 'pie', + data: [ {value: 30}, {value: 35}, {value: 35} ] + }); + $('#test-8 button').click(function(e) { + var margin = margins[parseInt($(e.target).attr('data-index'))]; + chart.option('margin', margin); + }); + }); + </script> + + <!-- Test 9 --> + <div id="test-9" class="test"> + <h2>9. Inner Changes</h2> + <div class="epoch"></div> + <p> + <button data-index="0">No Inner</button> + <button data-index="1">Small Inner</button> + <button data-index="2">Big Inner</button> + </p> + </div> + <script> + $(function() { + var innerMargins = [0, 50, 100]; + var chart = $('#test-9 .epoch').epoch({ + type: 'pie', + data: [ {value: 30}, {value: 35}, {value: 35} ] + }); + $('#test-9 button').click(function(e) { + var inner = innerMargins[parseInt($(e.target).attr('data-index'))]; + chart.option('inner', inner); + }); + }); + </script> + + <!-- Test 10 --> + <div id="test-10" class="test"> + <h2>10. Show/Hide Layers</h2> + <div class="epoch"></div> + <p> + <button data-index="0">Toggle A</button> + <button data-index="1">Toggle B</button> + <button data-index="2">Toggle C</button> + </p> + </div> + <script> + $(function() { + var chart = $('#test-10 .epoch').epoch({ + type: 'pie', + data: [ + {label: 'A', value: 20}, + {label: 'B', value: 45}, + {label: 'C', value: 35} + ] + }); + $('#test-10 button').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.toggleLayer(index); + }); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/basic/scatter.html b/debian/missing-sources/epoch/tests/render/basic/scatter.html new file mode 100644 index 0000000..31f1e42 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/scatter.html @@ -0,0 +1,398 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <script src="../js/data.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + </head> + <body> + <h1>Basic Scatter Plot Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Basic Scatter</p> + <ol> + <li><a href="#test-1">Single Series</a></li> + <li><a href="#test-2">Multi Series</a></li> + <li><a href="#test-3">Single Series Transition</a></li> + <li><a href="#test-4">Multi Series Transition</a></li> + <li><a href="#test-5">Multi Series Transition II</a></li> + <li><a href="#test-6">Color Override</a></li> + <li><a href="#test-7">Categorical Colors</a></li> + <li><a href="#test-8">Multi Series without Labels</a></li> + <li><a href="#test-9">Single Series with Radius</a></li> + <li><a href="#test-10">Radius Change</a></li> + <li><a href="#test-11">Show/Hide Layers</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Single Series</h2> + <p>Render a single random series scatter plot.</p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = [], + length = 128; + + for (var j = 0; j < length; j++) { + var x = Math.random() * 100, + y = Math.random() * 20; + data.push({x: x, y: y}); + } + + $('#test-1 .epoch').epoch({ type: 'scatter', data: [{label: 'A', values: data}]}); + }); + </script> + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Multi Series</h2> + <p>Render three random scatter series in the same plot.</p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 64; + + for (var i = 0; i < length; i++) { + for (var j = 0; j < data.length; j++) { + var x = Math.random() * 100, + y = Math.random() * 20; + data[j].values.push({x: x, y: y}); + } + } + + $('#test-2 .epoch').epoch({type: 'scatter', data: data}); + }); + </script> + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Single Series Transition</h2> + <p> + Transition from one random series to another random series. Use the + buttons below the plot to initiate the transition. + </p> + <div class="epoch"></div> + <p> + <button data-index="0">Series A</button> + <button data-index="1">Series B</button> + </p> + </div> + <script> + $(function() { + var data = [ + [ + {label: 'A', values: []} + ], + [ + {label: 'A', values: []} + ] + ], + length = 64; + + for (var i = 0; i < length; i++) { + for (var j = 0; j < data.length; j++) { + for (var k = 0; k < data[j].length; k++) { + var x = Math.random() * 100, + y = Math.random() * 20; + data[j][k].values.push({x: x, y: y}); + } + } + } + + var chart = $('#test-3 .epoch').epoch({type: 'scatter', data: data[0]}); + $('#test-3 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Multi Series Transition</h2> + <p> + Transition from a set of multiple randoms series random series to another of multiple random series. Use the + buttons below the plot to initiate the transition. + </p> + <div class="epoch"></div> + <p> + <button data-index="0">Series Set A</button> + <button data-index="1">Series Set B</button> + </p> + </div> + <script> + $(function() { + var data = [ + [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ] + ], + length = 64; + + for (var i = 0; i < length; i++) { + for (var j = 0; j < data.length; j++) { + for (var k = 0; k < data[j].length; k++) { + var x = Math.random() * 100, + y = Math.random() * 20; + data[j][k].values.push({x: x, y: y}); + } + } + } + + var chart = $('#test-4 .epoch').epoch({type: 'scatter', data: data[0]}); + $('#test-4 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Multi Series Transition II</h2> + <p> + Transition from a multi-series set of random data to a single series set of random data. + </p> + <div class="epoch"></div> + <p> + <button data-index="0">Series Set A</button> + <button data-index="1">Series Set B</button> + </p> + </div> + <script> + $(function() { + var data = [ + [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + [ + {label: 'A', values: []} + ] + ], + length = 64; + + for (var i = 0; i < length; i++) { + for (var j = 0; j < data.length; j++) { + for (var k = 0; k < data[j].length; k++) { + var x = Math.random() * 100, + y = Math.random() * 20; + data[j][k].values.push({x: x, y: y}); + } + } + } + + var chart = $('#test-5 .epoch').epoch({type: 'scatter', data: data[0]}); + $('#test-5 button').on('click', function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.update(data[index]); + }); + }); + </script> + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>6. Color Override</h2> + <p>The first series should be pink, the second green, and thrid blue.</p> + <div class="epoch"></div> + </div> + <style> + #test-6 .a .dot { fill: pink; } + #test-6 .b .dot { fill: green; } + #test-6 .c .dot { fill: blue; } + </style> + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 64; + + for (var i = 0; i < length; i++) { + for (var j = 0; j < data.length; j++) { + var x = Math.random() * 100, + y = Math.random() * 20; + data[j].values.push({x: x, y: y}); + } + } + + $('#test-6 .epoch').epoch({type: 'scatter', data: data}); + }); + </script> + + <!-- Test 7 --> + <div id="test-7" class="test"> + <h2>7. Categorical Colors</h2> + <p> + Correctly transition between different categorical color sets. + </p> + <div class="epoch"></div> + <p> + <button data-class="category10">category10</button> + <button data-class="category20">category20</button> + <button data-class="category20b">category20b</button> + <button data-class="category20c">category20c</button> + </p> + </div> + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []}, + {label: 'E', values: []}, + {label: 'F', values: []}, + {label: 'G', values: []}, + {label: 'H', values: []}, + {label: 'I', values: []}, + {label: 'J', values: []}, + {label: 'K', values: []} + ], + className = 'category10'; + + for (var i = 0; i < data.length; i++) { + var x = i * Math.PI * 2 / data.length, + y = Math.sin(x); + data[i].values.push({x: x, y: y}); + } + + $('#test-7 .epoch').epoch({ + type: 'scatter', + radius: 10, + data: data + }); + + + $('#test-7 button').on('click', function(e) { + $('#test-7 .epoch').removeClass(className); + className = $(e.target).attr('data-class'); + $('#test-7 .epoch').addClass(className); + }); + }); + </script> + + <!-- Test 8 --> + <div id="test-8" class="test"> + <h2>8. Multi Series without Labels</h2> + <p>Correctly render two random scatter plots when labels are not specified for the layers.</p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = [{values: []}, {values: []}], + length = 64; + + for (var i = 0; i < length; i++) { + for (var j = 0; j < data.length; j++) { + var x = Math.random() * 100, + y = Math.random() * 20; + data[j].values.push({x: x, y: y}); + } + } + + $('#test-8 .epoch').epoch({type: 'scatter', data: data}); + }); + </script> + + <!-- Test 9 --> + <div id="test-9" class="test"> + <h2>9. Single Series with Radius</h2> + <p>Render a single random series scatter plot with different radii.</p> + <div class="epoch"></div> + <p> + <button>Regenerate</button> + </p> + </div> + <script> + $(function() { + var length = 128; + + var generateData = function(length) { + var data = []; + for (var j = 0; j < length; j++) { + var x = Math.random() * 100, + y = Math.random() * 20, + r = Math.random() * 6 + 2; + data.push({x: x, y: y, r: r}); + } + return [{label: 'A', values: data}]; + }; + + var chart = $('#test-9 .epoch').epoch({ type: 'scatter', data: generateData(length)}); + + $("#test-9 p button:eq(0)").click(function() { + chart.update(generateData(length)); + }); + }); + </script> + + <!-- Test 10 --> + <div id="test-10" class="test"> + <h2>10. Radius Change</h2> + <div class="epoch"></div> + <p> + <button data-index="0">Small</button> + <button data-index="1">Normal</button> + <button data-index="2">Large</button> + <button data-index="3">Very Large</button> + </p> + </div> + <script> + $(function() { + var radii = [2.0, 3.5, 7, 14]; + var chart = $('#test-10 .epoch').epoch({ + type: 'scatter', + data: data().random() + }); + $('#test-10 button').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.option('radius', radii[index]); + }); + }); + </script> + + <!-- Test 11 --> + <div id="test-11" class="test"> + <h2>11. Show/Hide Layers</h2> + <div class="epoch"></div> + <p> + <button data-index="0">Toggle A</button> + <button data-index="1">Toggle B</button> + <button data-index="2">Toggle C</button> + </p> + </div> + <script> + $(function() { + var chart = $('#test-11 .epoch').epoch({ + type: 'scatter', + data: data().multiRandom(3) + }); + + $('#test-11 button').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.toggleLayer(index); + }); + }); + </script> + + </body> +</html> diff --git a/debian/missing-sources/epoch/tests/render/css/tests.css b/debian/missing-sources/epoch/tests/render/css/tests.css new file mode 100644 index 0000000..7f4099e --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/css/tests.css @@ -0,0 +1,12 @@ +body { + font-family: helvetica; +} + +/* Default test chart height of 220px. */ +.test .epoch { + height: 220px; +} + +.broken { + color: red; +} diff --git a/debian/missing-sources/epoch/tests/render/index.html b/debian/missing-sources/epoch/tests/render/index.html new file mode 100644 index 0000000..1ab15c1 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/index.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html> + <head> + <title>Epoch - Chart Rendering Tests</title> + <link rel="stylesheet" type="text/css" href="css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../dist/js/epoch.js"></script> + <link rel="stylesheet" type="text/css" href="../../dist/css/epoch.css"> + </head> + <body> + <h1>Epoch Chart Rendering Tests</h1> + + <h2>Basic Charts</h2> + <p> + <ul> + <li><a href="basic/area.html">Area</a></li> + <li><a href="basic/bar.html">Bar</a></li> + <li> + <ul><li><a href="basic/histogram.html">Histogram</a></li></ul> + </li> + <li><a href="basic/line.html">Line</a></li> + <li><a href="basic/pie.html">Pie</a></li> + <li><a href="basic/scatter.html">Scatter</a></li> + <li><a href="basic/options.html">All Charts - Options and Events</a></li> + </ul> + </p> + + <h2>Real-time Charts</h2> + <p> + <ul> + <li><a href="real-time/area.html">Area</a></li> + <li><a href="real-time/bar.html">Bar</a></li> + <li><a href="real-time/gauge.html">Gauge</a></li> + <li><a href="real-time/heatmap.html">Heatmap</a></li> + <li><a href="real-time/line.html">Line</a></li> + <li><a href="real-time/options.html">All Charts - Options and Events</a></li> + </ul> + </p> + + <h2>Themes</h2> + <p> + <ul> + <li><a href="themes/default.html">Default</a></li> + <li><a href="themes/dark.html">Dark</a></li> + </ul> + </p> + + <h2>Data / Models</h2> + <p> + <ul> + <li><a href="basic/model.html">Basic Model</a></li> + <li><a href="real-time/model.html">Real-time Model</a></li> + </ul> + </p> + </body> +</html> diff --git a/debian/missing-sources/epoch/tests/render/js/data.js b/debian/missing-sources/epoch/tests/render/js/data.js new file mode 100644 index 0000000..a49da68 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/js/data.js @@ -0,0 +1,210 @@ +(function() { + + // Quick data generator + Data = function() { + this.layers = [] + }; + + Data.prototype.add = function(fn) { + fn = fn ? fn : function(x) { return x; }; + this.layers.push(fn); + return this; + }; + + Data.prototype.get = function(domain, step) { + domain = domain ? domain : [0, 10]; + step = step ? step : 1; + + var data = [] + for (var i = 0; i < this.layers.length; i++) { + layer = { label: String.fromCharCode(i + 65), values: [] }; + for (var x = domain[0]; x < domain[1]; x += step) { + layer.values.push({ x: x, y: this.layers[i](x) }); + } + data.push(layer); + } + return data; + }; + + Data.prototype.random = function(entries, domain, range) { + entries = entries ? entries : 50; + domain = domain ? domain : [0, 100]; + range = range ? range : [0, 100]; + + var values = []; + for (var i = 0; i < entries; i++) { + var x = (domain[1] - domain[0]) * Math.random() + domain[0], + y = (range[1] - range[0]) * Math.random() + range[1]; + values.push({ x: x, y: y }); + } + + return [{ label: 'A', values: values }]; + }; + + Data.prototype.multiRandom = function(numSeries, entries, domain, range) { + numSeries = numSeries ? numSeries : 3; + entries = entries ? entries : 50; + domain = domain ? domain : [0, 100]; + range = range ? range : [0, 100]; + + var data = []; + + for (var j = 0; j < numSeries; j++) { + var layer = { label: String.fromCharCode(65 + j), values: [] }; + for (var i = 0; i < entries; i++) { + var x = (domain[1] - domain[0]) * Math.random() + domain[0], + y = (range[1] - range[0]) * Math.random() + range[1]; + layer.values.push({ x: x, y: y }); + } + data.push(layer); + } + + return data; + }; + + window.data = function() { return new Data(); }; + + + // Quick real-time data generator + Time = function() { + Data.call(this); + }; + + Time.prototype = new Data() + + Time.prototype.get = function(domain, step) { + var data = Data.prototype.get.apply(this, arguments), + time = parseInt(new Date().getTime() / 1000); + + for (var i = 0; i < data[0].values.length; i++) { + for (var j = 0; j < this.layers.length; j++) { + delete data[j].values[i].x; + data[j].values[i].time = time + i; + } + } + + this.currentTime = time; + this.lastX = domain[1]; + + return data; + }; + + Time.prototype.next = function(step) { + this.currentTime++; + this.lastX += (step ? step : 1); + + var data = []; + for (var j = 0; j < this.layers.length; j++) { + data.push({ time: this.currentTime, y: this.layers[j](this.lastX) }) + } + + return data; + } + + window.time = function() { return new Time(); }; + + + + + window.nextTime = (function() { + var currentTime = parseInt(new Date().getTime() / 1000); + return function() { return currentTime++; } + })(); + + + /* + * Normal distribution random histogram data generator. + */ + var NormalData = function(layers) { + this.layers = layers; + this.timestamp = ((new Date()).getTime() / 1000)|0; + }; + + var normal = function() { + var U = Math.random(), + V = Math.random(); + return Math.sqrt(-2*Math.log(U)) * Math.cos(2*Math.PI*V); + }; + + NormalData.prototype.sample = function() { + return parseInt(normal() * 12.5 + 50); + } + + NormalData.prototype.rand = function() { + var histogram = {}; + + for (var i = 0; i < 1000; i ++) { + var r = this.sample(); + if (!histogram[r]) { + histogram[r] = 1; + } + else { + histogram[r]++; + } + } + + return histogram; + }; + + NormalData.prototype.history = function(entries) { + if (typeof(entries) != 'number' || !entries) { + entries = 60; + } + + var history = []; + for (var k = 0; k < this.layers; k++) { + history.push({ label: String.fromCharCode(65+k), values: [] }); + } + + for (var i = 0; i < entries; i++) { + for (var j = 0; j < this.layers; j++) { + history[j].values.push({time: this.timestamp, histogram: this.rand()}); + } + this.timestamp++; + } + + return history; + }; + + NormalData.prototype.next = function() { + var entry = []; + for (var i = 0; i < this.layers; i++) { + entry.push({ time: this.timestamp, histogram: this.rand() }); + } + this.timestamp++; + return entry; + } + + window.NormalData = NormalData; + + + /* + * Beta distribution histogram data generator. + */ + var BetaData = function(alpha, beta, layers) { + this.alpha = alpha; + this.beta = beta; + this.layers = layers; + this.timestamp = ((new Date()).getTime() / 1000)|0; + }; + + BetaData.prototype = new NormalData(); + + BetaData.prototype.sample = function() { + var X = 0, + Y = 0; + + for (var j = 1; j <= this.alpha; j++) + X += -Math.log(1 - Math.random()); + + for (var j = 1; j <= this.beta; j++) + Y += -Math.log(1 - Math.random()); + + return parseInt(100 * X / (X + Y)); + } + + window.BetaData = BetaData; + +})(); + + diff --git a/debian/missing-sources/epoch/tests/render/real-time/area.html b/debian/missing-sources/epoch/tests/render/real-time/area.html new file mode 100644 index 0000000..b66ab7d --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/area.html @@ -0,0 +1,494 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + + <script> + var nextTime = (function() { + var currentTime = parseInt(new Date().getTime() / 1000); + return function() { return currentTime++; } + })(); + </script> + </head> + <body> + <h1>Real-time Area Plot Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Real-time Area</p> + <ol> + <li><a href="#test-1">Single Series</a></li> + <li><a href="#test-2">Single Series Single Transition</a></li> + <li><a href="#test-3">Single Seires Stream</a></li> + <li><a href="#test-4">Multi Series</a></li> + <li><a href="#test-5">Multi Series Single Transition</a></li> + <li><a href="#test-6">Multi Seires Stream</a></li> + <li><a href="#test-7">Color Override</a></li> + <li><a href="#test-8">Categorical Colors</a></li> + <li><a href="#test-9">Show/Hide Layers</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Single Series</h2> + <p> + Correctly render a single series plot of <code>y = cos(x) + 1</code> over the range <code>[0, 2π]</code>. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x) + 1, + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + $('#test-1 .epoch').epoch({ type: 'time.area', data: data }); + }); + </script> + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Single Series Single Transition</h2> + <p> + Correctly render a single series plot of <code>y = sin(x) + 1</code>. When the button is pressed push a new data point to the chart and correctly animate/render the transiton. + </p> + <div class="epoch"></div> + <p><button>Next</button></p> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40, + nextIndex = length; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.sin(x) + 1, + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + var chart = $('#test-2 .epoch').epoch({ + type: 'time.area', + data: data + }); + + $('#test-2 button').on('click', function(e) { + var x = nextIndex * 2 * Math.PI / length, + y = Math.sin(x) + 1, + time = nextTime(); + nextIndex++; + chart.push([{ time: time, y: y }]); + }); + }); + </script> + + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Single Seires Stream</h2> + <p>Correctly play / pause a single series stream of values from the plot <code>y = cos(x) + 1</code>.</p> + <div class="epoch"></div> + <p><button>Play</button></p> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40, + nextIndex = length, + playing = false, + interval = null; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x) + 1, + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + var chart = $('#test-3 .epoch').epoch({ + type: 'time.area', + data: data + }); + + var pushPoint = function() { + var x = nextIndex * 2 * Math.PI / length, + y = Math.cos(x) + 1, + time = nextTime(); + chart.push([{ time: time, y: y}]); + nextIndex++; + }; + + $('#test-3 button').on('click', function(e) { + if (playing) { + $(e.target).text('Play'); + clearInterval(interval); + } + else { + $(e.target).text('Pause'); + pushPoint(); + interval = setInterval(pushPoint, 1000); + } + playing = !playing; + }); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Multi Series</h2> + <p>Correctly render a multi series plot of the following functions:</p> + <ul> + <li><code>y = log(x+1)</code></li> + <li><code>y = 2*log(x+1)</code></li> + <li><code>y = 3*log(x+1)</code></li> + </ul> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i + 1, + time = nextTime(); + for (var j = 0; j < data.length; j++) { + data[j].values.push({time: time, y: (j+1) * Math.log(x)}); + } + } + + $('#test-4 .epoch').epoch({ + type: 'time.area', + data: data, + axes: ['left', 'right', 'bottom'] + }); + }); + </script> + + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Multi Series Single Transition</h2> + <p> + Correctly render a multi series plot of the following functions: + <ul> + <li><code>y = x</code></li> + <li><code>y = x<sup>1.25</sup></code></li> + <li><code>y = x<sup>1.5</sup></code></li> + </ul> + and correctly render/animate the transiton after pressing the "Next" button. + </p> + <div class="epoch"></div> + <p> + <button>Next</button> + </p> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40, + scale = 0.1, + nextIndex = length; + + for (var i = 0; i < length; i++) { + var x = i * scale, + time = nextTime(); + data[0].values.push({time: time, y: x}); + data[1].values.push({time: time, y: Math.pow(x, 1.25)}); + data[2].values.push({time: time, y: Math.pow(x, 1.5)}); + } + + var chart = $('#test-5 .epoch').epoch({ + type: 'time.area', + data: data, + axes: ['left', 'right', 'bottom'] + }); + + $('#test-5 button').on('click', function(e) { + var x = nextIndex * scale, + time = nextTime(); + nextIndex++; + chart.push([ + {time: time, y: x}, + {time: time, y: Math.pow(x, 1.25)}, + {time: time, y: Math.pow(x, 1.5)} + ]); + }); + }); + </script> + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>6. Multi Seires Stream</h2> + <p> + Correctly play / pause a multi series stream of values over the following plots: + <ul> + <li><code>x</code></li> + <li><code>x * log(x)</code></li> + <li><code>x * log<sup>2</sup>(x)</code></li> + </ul> + </p> + <div class="epoch"></div> + <p><button>Play</button></p> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40, + nextIndex = length, + scale = 0.1, + playing = false, + interval = null; + + for (var i = 0; i < length; i++) { + var x = (i+1) * scale, + time = nextTime(); + data[0].values.push({time: time, y: x}); + data[1].values.push({time: time, y: x * Math.log(x)}); + data[2].values.push({time: time, y: x * Math.pow(Math.log(x), 2)}); + } + + var chart = $('#test-6 .epoch').epoch({ + type: 'time.area', + data: data, + axes: ['right', 'bottom'] + }); + + var pushPoint = function() { + var x = (nextIndex +1) * scale, + time = nextTime(); + chart.push([ + { time: time, y: x}, + { time: time, y: x * Math.log(x)}, + { time: time, y: x * Math.pow(Math.log(x), 2)} + ]); + nextIndex++; + }; + + $('#test-6 button').on('click', function(e) { + if (playing) { + $(e.target).text('Play'); + clearInterval(interval); + } + else { + $(e.target).text('Pause'); + interval = setInterval(pushPoint, 1000); + pushPoint(); + } + playing = !playing; + }); + }); + </script> + + <!-- Test 7 --> + <div id="test-7" class="test"> + <h2>7. Color Override</h2> + <p>The first layer should pink, the second green, and the third blue.</p> + <div id="test-7-plot" class="epoch"></div> + </div> + + <style> + #test-7-plot .a .area { fill: pink; } + #test-7-plot .b .area { fill: green; } + #test-7-plot .c .area { fill: blue; } + </style> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i + 1, + time = nextTime(); + for (var j = 0; j < data.length; j++) { + data[j].values.push({time: time, y: (j+1) * Math.log(x)}); + } + } + + $('#test-7 .epoch').epoch({ + type: 'time.area', + data: data + }); + }); + </script> + + <!-- Test 8 --> + <div id="test-8" class="test"> + <h2>8. Categorical Colors</h2> + <p>Correctly render and switch between different categorical colors. Unlike with the basic charts this doesn't occur just by switching the class name on the containing element. The CSS query engine must be purge and the plot must be manually redrawn, like so:<code><pre> +Epoch.QueryCSS.purge(); +chart.draw(); +</pre></code> + </p> + + <div class="epoch category10"></div> + + <p> + <button data-class="category10">category10</button> + <button data-class="category20">category20</button> + <button data-class="category20b">category20b</button> + <button data-class="category20c">category20c</button> + </p> + </div> + + <script> + $(function() { + var data = [], + length = 41, + className = "category10", + layers = 8, + time = nextTime(); + + for (var i = 0; i < layers; i++) { + var layer = { label: String.fromCharCode(i+65), values: [] }; + for (var j = 0; j < length; j++) { + var x = j + 1, + y = Math.pow(x, 1 + 0.3 * Math.log(Math.log(Math.log(x+1) + 1) + 1)); + layer.values.push({ time: time + j, y: y }); + } + data.push(layer); + } + + var chart = $('#test-8 .epoch').epoch({ type: 'time.area', data: data }); + + $('#test-8 button').on('click', function(e) { + $('#test-8 .epoch').removeClass(className); + className = $(e.target).attr('data-class'); + $('#test-8 .epoch').addClass(className); + Epoch.QueryCSS.purge(); + chart.draw(); + }); + }); + </script> + + <!-- Test 9 --> + <div id="test-9" class="test"> + <h2>9. Show/Hide Layers</h2> + <div class="epoch"></div> + <p> + <button class="toggle" data-index="0">Toggle A</button> + <button class="toggle" data-index="1">Toggle B</button> + <button class="toggle" data-index="2">Toggle C</button> | + <button class="playback">Play</button> + </p> + </div> + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40, + nextIndex = length, + scale = 0.1, + playing = false, + interval = null; + + for (var i = 0; i < length; i++) { + var x = (i+1) * scale, + time = nextTime(); + data[0].values.push({time: time, y: x}); + data[1].values.push({time: time, y: 1.5*x}); + data[2].values.push({time: time, y: 2*x}); + } + + var chart = $('#test-9 .epoch').epoch({ + type: 'time.area', + data: data, + axes: ['right', 'bottom'] + }); + + var pushPoint = function() { + var x = (nextIndex +1) * scale, + time = nextTime(); + chart.push([ + { time: time, y: x}, + { time: time, y: 1.5*x}, + { time: time, y: 2*x} + ]); + nextIndex++; + }; + + $('#test-9 button.toggle').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.toggleLayer(index); + }); + + $('#test-9 button.playback').on('click', function(e) { + if (playing) { + $(e.target).text('Play'); + clearInterval(interval); + } + else { + $(e.target).text('Pause'); + interval = setInterval(pushPoint, 1000); + pushPoint(); + } + playing = !playing; + }); + }); + </script> + + <!-- Test 10 --> + <div id="test-10" class="test"> + <h2>10. Fixed Range</h2> + <p> + Render a single series plot of <code>y = cos(x)</code>. Instead of automatically setting the range to <code>[-1, 1]</code>, the range is manually set to <code>[-2, 5]</code>. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x), + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + $('#test-10 .epoch').epoch({ + type: 'time.area', + axes: ['right', 'bottom'], + data: data, + range: [-2, 5] + }); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/real-time/bar.html b/debian/missing-sources/epoch/tests/render/real-time/bar.html new file mode 100644 index 0000000..952119c --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/bar.html @@ -0,0 +1,484 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + + <script> + var nextTime = (function() { + var currentTime = parseInt(new Date().getTime() / 1000); + return function() { return currentTime++; } + })(); + </script> + </head> + <body> + <h1>Real-time Bar Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Real-time Bar</p> + <ol> + <li><a href="#test-1">Single Series</a></li> + <li><a href="#test-2">Single Series Single Transition</a></li> + <li><a href="#test-3">Single Seires Stream</a></li> + <li><a href="#test-4">Multi Series</a></li> + <li><a href="#test-5">Multi Series Single Transition</a></li> + <li><a href="#test-6">Multi Seires Stream</a></li> + <li><a href="#test-7">Color Override</a></li> + <li><a href="#test-8">Categorical Colors</a></li> + <li><a href="#test-9">Show/Hide Layers</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Single Series</h2> + <p> + Correctly render a single series plot of <code>y = cos(x) + 1</code> over the range <code>[0, 2π]</code>. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x) + 1, + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + $('#test-1 .epoch').epoch({ type: 'time.bar', data: data }); + }); + </script> + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Single Series Single Transition</h2> + <p> + Correctly render a single series plot of <code>y = sin(x) + 1</code>. When the button is pressed push a new data point to the chart and correctly animate/render the transiton. + </p> + <div class="epoch"></div> + <p><button>Next</button></p> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40, + nextIndex = length; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.sin(x) + 1, + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + var chart = $('#test-2 .epoch').epoch({ + type: 'time.bar', + data: data + }); + + $('#test-2 button').on('click', function(e) { + var x = nextIndex * 2 * Math.PI / length, + y = Math.sin(x) + 1, + time = nextTime(); + nextIndex++; + chart.push([{ time: time, y: y }]); + }); + }); + </script> + + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Single Seires Stream</h2> + <p>Correctly play / pause a single series stream of values from the plot <code>y = cos(x) + 1</code>.</p> + <div class="epoch"></div> + <p><button>Play</button></p> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40, + nextIndex = length, + playing = false, + interval = null; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x) + 1, + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + var chart = $('#test-3 .epoch').epoch({ + type: 'time.bar', + data: data + }); + + var pushPoint = function() { + var x = nextIndex * 2 * Math.PI / length, + y = Math.cos(x) + 1, + time = nextTime(); + chart.push([{ time: time, y: y}]); + nextIndex++; + }; + + $('#test-3 button').on('click', function(e) { + if (playing) { + $(e.target).text('Play'); + clearInterval(interval); + } + else { + $(e.target).text('Pause'); + pushPoint(); + interval = setInterval(pushPoint, 1000); + } + playing = !playing; + }); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Multi Series</h2> + <p>Correctly render a multi series plot of the following functions:</p> + <ul> + <li><code>y = log(x+1)</code></li> + <li><code>y = 2*log(x+1)</code></li> + <li><code>y = 3*log(x+1)</code></li> + </ul> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i + 1, + time = nextTime(); + for (var j = 0; j < data.length; j++) { + data[j].values.push({time: time, y: (j+1) * Math.log(x)}); + } + } + + $('#test-4 .epoch').epoch({ + type: 'time.bar', + data: data, + axes: ['left', 'right', 'bottom'] + }); + }); + </script> + + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Multi Series Single Transition</h2> + <p> + Correctly render a multi series plot of the following functions: + <ul> + <li><code>y = x</code></li> + <li><code>y = x<sup>1.25</sup></code></li> + <li><code>y = x<sup>1.5</sup></code></li> + </ul> + and correctly render/animate the transiton after pressing the "Next" button. + </p> + <div class="epoch"></div> + <p> + <button>Next</button> + </p> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40, + scale = 0.1, + nextIndex = length; + + for (var i = 0; i < length; i++) { + var x = i * scale, + time = nextTime(); + data[0].values.push({time: time, y: x}); + data[1].values.push({time: time, y: Math.pow(x, 1.25)}); + data[2].values.push({time: time, y: Math.pow(x, 1.5)}); + } + + var chart = $('#test-5 .epoch').epoch({ + type: 'time.bar', + data: data, + axes: ['left', 'right', 'bottom'] + }); + + $('#test-5 button').on('click', function(e) { + var x = nextIndex * scale, + time = nextTime(); + nextIndex++; + chart.push([ + {time: time, y: x}, + {time: time, y: Math.pow(x, 1.25)}, + {time: time, y: Math.pow(x, 1.5)} + ]); + }); + }); + </script> + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>6. Multi Seires Stream</h2> + <p> + Correctly play / pause a multi series stream of values over the following plots: + <ul> + <li><code>x</code></li> + <li><code>x * log(x)</code></li> + <li><code>x * log<sup>2</sup>(x)</code></li> + </ul> + </p> + <div class="epoch"></div> + <p><button>Play</button></p> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40, + nextIndex = length, + scale = 0.1, + playing = false, + interval = null; + + for (var i = 0; i < length; i++) { + var x = (i+1) * scale, + time = nextTime(); + data[0].values.push({time: time, y: x}); + data[1].values.push({time: time, y: x * Math.log(x)}); + data[2].values.push({time: time, y: x * Math.pow(Math.log(x), 2)}); + } + + var chart = $('#test-6 .epoch').epoch({ + type: 'time.bar', + data: data, + axes: ['right', 'bottom'] + }); + + var pushPoint = function() { + var x = (nextIndex +1) * scale, + time = nextTime(); + chart.push([ + { time: time, y: x}, + { time: time, y: x * Math.log(x)}, + { time: time, y: x * Math.pow(Math.log(x), 2)} + ]); + nextIndex++; + }; + + $('#test-6 button').on('click', function(e) { + if (playing) { + $(e.target).text('Play'); + clearInterval(interval); + } + else { + $(e.target).text('Pause'); + interval = setInterval(pushPoint, 1000); + pushPoint(); + } + playing = !playing; + }); + }); + </script> + + <!-- Test 7 --> + <div id="test-7" class="test"> + <h2>7. Color Override</h2> + <p>The first layer should pink, the second green, and the third blue.</p> + <div id="test-7-plot" class="epoch"></div> + </div> + + <style> + #test-7-plot .bar.a { fill: pink; } + #test-7-plot .bar.b { fill: green; } + #test-7-plot .bar.c { fill: blue; } + </style> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i + 1, + time = nextTime(); + for (var j = 0; j < data.length; j++) { + data[j].values.push({time: time, y: (j+1) * Math.log(x)}); + } + } + + $('#test-7 .epoch').epoch({ + type: 'time.bar', + data: data + }); + }); + </script> + + <!-- Test 8 --> + <div id="test-8" class="test"> + <h2>8. Categorical Colors</h2> + <p>Correctly render and switch between different categorical colors. Unlike with the basic charts this doesn't occur just by switching the class name on the containing element. The CSS query engine must be purge and the plot must be manually redrawn, like so:<code><pre> +Epoch.QueryCSS.purge(); +chart.draw(); +</pre></code> + </p> + + <div class="epoch category10"></div> + + <p> + <button data-class="category10">category10</button> + <button data-class="category20">category20</button> + <button data-class="category20b">category20b</button> + <button data-class="category20c">category20c</button> + </p> + </div> + + <script> + $(function() { + var data = [], + length = 41, + className = "category10", + layers = 8, + time = nextTime(); + + for (var i = 0; i < layers; i++) { + var layer = { label: String.fromCharCode(i+65), values: [] }; + for (var j = 0; j < length; j++) { + var x = j + 1, + y = Math.pow(x, 1 + 0.3 * Math.log(Math.log(Math.log(x+1) + 1) + 1)); + layer.values.push({ time: time + j, y: y }); + } + data.push(layer); + } + + var chart = $('#test-8 .epoch').epoch({ type: 'time.bar', data: data }); + + $('#test-8 button').on('click', function(e) { + $('#test-8 .epoch').removeClass(className); + className = $(e.target).attr('data-class'); + $('#test-8 .epoch').addClass(className); + Epoch.QueryCSS.purge(); + chart.draw(); + }); + }); + </script> + + <!-- Test 9 --> + <div id="test-9" class="test"> + <h2>9. Show/Hide Layers</h2> + <div class="epoch"></div> + <p> + <button class="toggle" data-index="0">Toggle A</button> + <button class="toggle" data-index="1">Toggle B</button> + <button class="toggle" data-index="2">Toggle C</button> | + <button class="playback">Play</button> + </p> + </div> + <script> + $(function() { + var data = [ + { label: 'A', values: [] }, + { label: 'B', values: [] }, + { label: 'C', values: [] } + ], + time = nextTime(), + j = 0, + interval = null; + + for (var i = 0; i < data.length; i++) { + for (j = 0; j < 60; j++) { + data[i].values.push({time: time+j, y: 0.25*(i+4)*j }) + } + } + + function nextPoint() { + var entry = [] + for (var i = 0; i < data.length; i++) { + entry.push({time: time+j, y: 0.25*(i+4)*j }); + } + chart.push(entry); + j++; + } + + var chart = $('#test-9 .epoch').epoch({ type: 'time.bar', data: data }); + + $('#test-9 .toggle').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.toggleLayer(index); + }); + + $('#test-9 .playback').click(function(e) { + if (interval == null) { + interval = setInterval(nextPoint, 1000); + nextPoint(); + $(e.target).text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $(e.target).text('Play'); + } + }); + }); + </script> + + <!-- Test 10 --> + <div id="test-10" class="test"> + <h2>10. Fixed Range</h2> + <p> + Render a single series plot of <code>y = cos(x) + 1</code>. Instead of automatically setting the range to <code>[0, 2]</code>, the range is manually set to <code>[0, 5]</code>. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x) + 1, + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + $('#test-10 .epoch').epoch({ + type: 'time.bar', + axes: ['right', 'bottom'], + data: data, + range: [0, 5] + }); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/real-time/gauge.html b/debian/missing-sources/epoch/tests/render/real-time/gauge.html new file mode 100644 index 0000000..614334d --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/gauge.html @@ -0,0 +1,283 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + + <script> + var nextTime = (function() { + var currentTime = parseInt(new Date().getTime() / 1000); + return function() { return currentTime++; } + })(); + </script> + </head> + <body> + <h1>Real-time Gauge Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Real-time Gauge</p> + <ol> + <li><a href="#test-1">Single Value Display</a></li> + <li><a href="#test-2">Single Transition</a></li> + <li><a href="#test-3">Stream Transition</a></li> + <li><a href="#test-4">Gauge Sizes</a></li> + <li><a href="#test-5">Color Override</a></li> + <li><a href="test-6">Option: domain</a></li> + <li><a href="test-7">Option: ticks</a></li> + <li><a href="test-8">Option: tickSize</a></li> + <li><a href="test-9">Option: tickOffset</a></li> + <li><a href="test-10">Option: format</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Single Value Display</h2> + <p>Display a single value of 25%</p> + <div class="epoch gauge-small"></div> + </div> + <script> + $(function() { + $('#test-1 .epoch').epoch({ type: 'time.gauge', value: 0.25 }); + }); + </script> + + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Single Transition</h2> + <p>Display value of 0% and transition to a random value when the button is pressed.</p> + <div class="epoch gauge-small"></div> + <p><button>Transition</button></p> + </div> + <script> + $(function() { + var chart = $('#test-2 .epoch').epoch({ + type: 'time.gauge', + value: 0.0 + }); + + $('#test-2 button').on('click', function(e) { + chart.update(Math.random()); + }); + }); + </script> + + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Stream Transition</h2> + <p>Display value of 0% and transition to a random value every second when the button is pressed.</p> + <div class="epoch gauge-small"></div> + <p><button>Play</button></p> + </div> + <script> + $(function() { + var chart = $('#test-3 .epoch').epoch({ + type: 'time.gauge', + value: 0.0 + }), + playing = false, + interval = null; + + $('#test-3 button').on('click', function(e) { + if (interval === null) { + interval = setInterval(function() { + chart.update(Math.random()); + }, 1000); + chart.update(Math.random()); + $(e.target).text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $(e.target).text('Play'); + } + }); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Gauge Sizes</h2> + <p>Display the four built-in gauge sizes in this order: tiny, small, medium, large.</p> + <div class="epoch gauge-tiny"></div> + <div class="epoch gauge-small"></div> + <div class="epoch gauge-medium"></div> + <div class="epoch gauge-large"></div> + </div> + <script> + $(function() { + $('#test-4 .gauge-tiny').epoch({ type: 'time.gauge', value: 0.75 }); + $('#test-4 .gauge-small').epoch({ type: 'time.gauge', value: 0.75 }); + $('#test-4 .gauge-medium').epoch({ type: 'time.gauge', value: 0.75 }); + $('#test-4 .gauge-large').epoch({ type: 'time.gauge', value: 0.75 }); + }); + </script> + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Color Override</h2> + <p> + Override the basic gauge styles with the following + <ul> + <li>Outer arc 8px thickness colored green</li> + <li>Inner arc 2px thickness colored orange</li> + <li>Ticks should be 3px in width and red</li> + <li>Needle colored blue.</li> + <li>Needle base colored black</li> + </ul> + </p> + <div id="test-5-plot" class="epoch gauge-medium"></div> + </div> + + <style> + #test-5-plot .epoch .gauge .arc.outer { + stroke-width: 8px; + stroke: green; + } + + #test-5-plot .epoch .gauge .arc.inner { + stroke-width: 2px; + stroke: orange; + } + + #test-5-plot .epoch .gauge .tick { + stroke-width: 3px; + stroke: red; + } + + #test-5-plot .epoch .gauge .needle { + fill: blue; + } + + #test-5-plot .epoch .gauge .needle-base { + fill: black; + } + </style> + + <script> + $(function() { + $('#test-5 .epoch').epoch({ type: 'time.gauge', value: 0.5 }); + }); + </script> + + <!-- + Common Option Event Listener Events + --> + <script> + window.makeOptionTest = function(testNumber, chartOptions, optionName) { + chartOptions = chartOptions ? chartOptions : {}; + + if (!chartOptions.type) + chartOptions.type = 'time.gauge'; + if (!chartOptions.value) + chartOptions.value = 0.5; + if (!chartOptions.domain) + chartOptions.domain = [0, 1]; + + var domain = chartOptions.domain, + id = '#test-' + testNumber, + chart = $(id + ' .epoch').epoch(chartOptions), + interval = null, + next = function() { chart.push(Math.random()*(domain[1] - domain[0]) + domain[0]); }; + + $(id + ' .playback').click(function(e) { + if (!interval) { + interval = setInterval(next, 1500); + next(); + $(e.target).text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $(e.target).text('Play'); + } + }); + + var formats = [ + Epoch.Formats.percent, + function(d) { return d.toFixed(2); } + ]; + + $(id + ' .option').click(function(e) { + var value = parseInt($(e.target).attr('data-value')); + + console.log(optionName, value); + + if (optionName == 'domain') + value = domain = $(e.target).attr('data-value').split(',').map(function(d) { return parseInt(d); }); + else if (optionName == 'format') + value = formats[value]; + chart.option(optionName, value); + }); + }; + </script> + + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>6. Option: domain</h2> + <div class="epoch gauge-small"></div> + <p> + <button class="playback">Play</button> | + <button class="option" data-value="0,1">[0, 1]</button> + <button class="option" data-value="0,2">[0, 2]</button> + </p> + </div> + <script>$(function() { makeOptionTest(6, {}, 'domain'); });</script> + + <!-- Test 7 --> + <div id="test-7" class="test"> + <h2>7. Option: ticks</h2> + <div class="epoch gauge-small"></div> + <p> + <button class="playback">Play</button> | + <button class="option" data-value="5">5 Ticks</button> + <button class="option" data-value="10">10 Ticks</button> + <button class="option" data-value="20">20 Ticks</button> + <button class="option" data-value="40">40 Ticks</button> + </p> + </div> + <script>$(function() { makeOptionTest(7, {}, 'ticks'); });</script> + + <!-- Test 8 --> + <div id="test-8" class="test"> + <h2>8. Option: tickSize</h2> + <div class="epoch gauge-small"></div> + <p> + <button class="playback">Play</button> | + <button class="option" data-value="2">2px</button> + <button class="option" data-value="5">5px</button> + <button class="option" data-value="10">10px</button> + <button class="option" data-value="20">20px</button> + </p> + </div> + <script>$(function() { makeOptionTest(8, {}, 'tickSize'); });</script> + + <!-- Test 9 --> + <div id="test-9" class="test"> + <h2>9. Option: tickOffset</h2> + <div class="epoch gauge-small"></div> + <p> + <button class="playback">Play</button> | + <button class="option" data-value="5">5px</button> + <button class="option" data-value="10">10px</button> + <button class="option" data-value="20">20px</button> + </p> + </div> + <script>$(function() { makeOptionTest(9, {}, 'tickOffset'); });</script> + + <!-- Test 10 --> + <div id="test-10" class="test"> + <h2>10. Option: format</h2> + <div class="epoch gauge-small"></div> + <p> + <button class="playback">Play</button> | + <button class="option" data-value="0">Percent</button> + <button class="option" data-value="1">Normal</button> + </p> + </div> + <script>$(function() { makeOptionTest(10, {}, 'format'); });</script> + </body> +</html> diff --git a/debian/missing-sources/epoch/tests/render/real-time/heatmap.html b/debian/missing-sources/epoch/tests/render/real-time/heatmap.html new file mode 100644 index 0000000..ddabe2e --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/heatmap.html @@ -0,0 +1,559 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <script src="../js/data.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + </head> + <body> + <h1>Real-time Heatmap Plot Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Real-time Heatmap</p> + <ol> + <li><a href="#test-1">Single Series - Normal</a></li> + <li><a href="#test-2">Single Series - Beta</a></li> + <li><a href="#test-3">Single Series - Normal, Single Update</a></li> + <li><a href="#test-4">Single Series - Beta, Stream Update</a></li> + <li><a href="#test-5">Single Series - Color Override</a></li> + <li><a href="#test-6">Multi Series - Normal + Beta</a></li> + <li><a href="#test-7">Multi Series Color Override</a></li> + <li><a href="#test-8">Range Independent Bucketing</a></li> + <li><a href="#test-9">Option: buckets</a></li> + <li><a href="#test-10">Option: bucketRange</a></li> + <li><a href="#test-11">Option: bucketPadding</a></li> + <li><a href="#test-12">Option: cutOutliers</a></li> + <li><a href="#test-13">Option: opacity</a></li> + <li><a href="#test-14">Show/Hide Layer</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Single Series - Normal</h2> + <p>Select random values from the normal distribution and display them with the heatmap.</p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = new NormalData(1); + $('#test-1 .epoch').epoch({ + type: 'time.heatmap', + data: data.history(120), + windowSize: 120, + buckets: 20 + }); + }); + </script> + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Single Series - Beta </h2> + <p>Select random values from the Beta(2, 5) distribution and display them with the heatmap.</p> + <div class="epoch"></div> + </div> + <script> + $(function() { + var data = new BetaData(2, 5, 1); + $('#test-2 .epoch').epoch({ + type: 'time.heatmap', + data: data.history(120), + windowSize: 120, + buckets: 20 + }); + }); + </script> + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Single Sieres - Normal, Single Update</h2> + <p> + Plot the normal distribution and transition a new element when the button is pressed. + </p> + <div class="epoch"></div> + <p><button>Next</button></p> + </div> + <script> + $(function() { + var data = new NormalData(1); + var chart = $('#test-3 .epoch').epoch({ + type: 'time.heatmap', + data: data.history(120), + windowSize: 120, + buckets: 20 + }); + $('#test-3 button').on('click', function(e) { + chart.push(data.next()); + }); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Single Series - Beta, Stream Update</h2> + <p> + Plot the Beta(2, 5) distribution and begin streaming new elements each second once the + button is pressed. + </p> + <div class="epoch"></div> + <button>Play</button> + </div> + + <script> + $(function() { + var data = new BetaData(2, 5, 1), + interval = null; + var chart = $('#test-4 .epoch').epoch({ + type: 'time.heatmap', + data: data.history(120), + windowSize: 120, + buckets: 20 + }); + $('#test-4 button').on('click', function(e) { + if (interval === null) { + chart.push(data.next()); + interval = setInterval(function() { + chart.push(data.next()); + }, 1000); + $(e.target).text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $(e.target).text('Play'); + } + }); + }); + </script> + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Single Series - Color Override</h2> + <p>Change the bucket base color to orange and plot the Beta(2, 2) distribution.</p> + <div id="test-5-plot" class="epoch"></div> + </div> + + <style> + #test-5-plot .a rect.bucket { fill: darkorange; } + </style> + + <script> + $(function() { + var data = new BetaData(2, 2, 1); + $('#test-5 .epoch').epoch({ + type: 'time.heatmap', + data: data.history(120), + windowSize: 120, + buckets: 20 + }); + }); + </script> + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>6. Multi Series - Normal + Beta</h2> + <p> + Plot the Beta(2,5) and Normal distributions as two layers in a single heatmap. Stream elements to the plot + by pressing the button. The normal distribution layer is in blue and the beta in green. + </p> + <div class="epoch"></div> + <button>Play</button> + </div> + <script> + $(function() { + var normal = new NormalData(1), + normalData = normal.history(120)[0], + beta = new BetaData(2, 5, 1), + betaData = beta.history(120)[0], + data = [normalData, betaData], + interval = null; + + var chart = $('#test-6 .epoch').epoch({ + type: 'time.heatmap', + data: data, + windowSize: 120, + buckets: 20 + }); + + function pushNext() { + chart.push([normal.next()[0], beta.next()[0]]); + } + + $('#test-6 button').on('click', function(e) { + if (interval == null) { + pushNext(); + interval = setInterval(pushNext, 1000); + $(e.target).text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $(e.target).text('Play'); + } + }); + }); + </script> + + <!-- Test 7 --> + <div id="test-7" class="test"> + <h2>7. Multi Series Color Override</h2> + <p> + Plot the normal distribution and the Beta(2, 5) distribution overrding normal to be in red, and beta to + be in purple. + </p> + <div id="test-7-plot" class="epoch"></div> + </div> + + <style> + #test-7-plot .a rect.bucket { fill: red; } + #test-7-plot .b rect.bucket { fill: purple; } + </style> + + <script> + $(function() { + var normal = new NormalData(1), + normalData = normal.history(120)[0], + beta = new BetaData(2, 5, 1), + betaData = beta.history(120)[0], + interval = null; + + normalData.label = 'A'; + betaData.label = 'B'; + + var chart = $('#test-7 .epoch').epoch({ + type: 'time.heatmap', + data: [normalData, betaData], + windowSize: 120, + buckets: 20 + }); + }); + </script> + + <!-- Test 8 --> + <div id="test-8" class="test"> + <h2>8. Range Independent Bucketing (<a href="https://github.com/fastly/epoch/issues/41">Issue #41</a>)</h2> + <p> + Discrete bucketing of sparse histogram values should produce similar looking graphs regardless + of numeric relation between the range of the plot and the number of buckets. + </p> + <h3>Range [0, 100] with 20 buckets</h3> + <div class="chart1 epoch"></div> + + <h3>Range [0, 100] with 45 buckets</h3> + <div class="chart2 epoch"></div> + </div> + <script> + $(function() { + var normal = new NormalData(1), + data = normal.history(120); + + console.log(data); + + $('#test-8 .chart1').epoch({ + type: 'time.heatmap', + data: data, + windowSize: 120, + buckets: 20 + }); + $('#test-8 .chart2').epoch({ + type: 'time.heatmap', + data: data, + windowSize: 120, + buckets: 45 + }); + }); + </script> + + <!-- Test 9 --> + <div id="test-9" class="test"> + <h2>9. Option: buckets</h2> + <div class="epoch"></div> + <p> + <button class="playback">Play</button> | + <button class="buckets" data-buckets="10">10 Buckets</button> + <button class="buckets" data-buckets="20">20 Buckets</button> + <button class="buckets" data-buckets="40">40 Buckets</button> + </p> + </div> + <script> + $(function() { + var beta = new BetaData(2, 5, 1), + data = beta.history(120); + + var chart = $('#test-9 .epoch').epoch({ + type: 'time.heatmap', + data: data, + buckets: 10 + }); + + var interval = null, + pushPoint = function () { chart.push(beta.next()); }; + + $('#test-9 .playback').click(function(e) { + if (interval == null) { + $(e.target).text('Pause'); + pushPoint(); + interval = setInterval(pushPoint, 1000); + } + else { + $(e.target).text('Play'); + clearInterval(interval); + interval = null; + } + }); + + $('#test-9 .buckets').click(function(e) { + var buckets = parseInt($(e.target).attr('data-buckets')); + chart.option('buckets', buckets); + }); + }); + </script> + + <!-- Test 10 --> + <div id="test-10" class="test"> + <h2>10. Option: bucketRange</h2> + <div class="epoch"></div> + <p> + <button class="playback">Play</button> | + <button class="bucketRange" data-bucket-range="0,100">[0, 100]</button> + <button class="bucketRange" data-bucket-range="0,75">[0, 75]</button> + <button class="bucketRange" data-bucket-range="0,50">[0, 50]</button> + <button class="bucketRange" data-bucket-range="0,25">[0, 25]</button> + <button class="bucketRange" data-bucket-range="10,75">[10,75]</button> + </p> + </div> + <script> + $(function() { + var beta = window.beta = new BetaData(2, 5, 1), + data = beta.history(120); + + var chart = $('#test-10 .epoch').epoch({ + type: 'time.heatmap', + data: data, + axes: ['left', 'right'], + buckets: 20, + windowSize: 100, + cutOutliers: true + }); + + var interval = null, + pushPoint = function () { chart.push(beta.next()); }; + + $('#test-10 .playback').click(function(e) { + if (interval == null) { + $(e.target).text('Pause'); + pushPoint(); + interval = setInterval(pushPoint, 1000); + } + else { + $(e.target).text('Play'); + clearInterval(interval); + interval = null; + } + }); + + $('#test-10 .bucketRange').click(function(e) { + var bucketRange = $(e.target).attr('data-bucket-range') + .split(',').map(function(d) { return parseInt(d); }); + chart.option('bucketRange', bucketRange); + }); + }); + </script> + + <!-- Test 11 --> + <div id="test-11" class="test"> + <h2>11. Option: bucketPadding</h2> + <div class="epoch"></div> + <p> + <button class="playback">Play</button> | + <button class="bucketPadding" data-bucket-padding="0">Padding: 0</button> + <button class="bucketPadding" data-bucket-padding="2">Padding: 2</button> + <button class="bucketPadding" data-bucket-padding="4">Padding: 4</button> + <button class="bucketPadding" data-bucket-padding="8">Padding: 8</button> + </p> + </div> + <script> + $(function() { + var beta = window.beta = new BetaData(2, 5, 1), + data = beta.history(120); + + var chart = $('#test-11 .epoch').epoch({ + type: 'time.heatmap', + data: data, + axes: [], + buckets: 15, + windowSize: 100 + }); + + var interval = null, + pushPoint = function () { chart.push(beta.next()); }; + + $('#test-11 .playback').click(function(e) { + if (interval == null) { + $(e.target).text('Pause'); + pushPoint(); + interval = setInterval(pushPoint, 1000); + } + else { + $(e.target).text('Play'); + clearInterval(interval); + interval = null; + } + }); + + $('#test-11 .bucketPadding').click(function(e) { + var bucketPadding = parseInt($(e.target).attr('data-bucket-padding')); + console.log(bucketPadding) + chart.option('bucketPadding', bucketPadding); + }); + }); + </script> + + <!-- Test 12 --> + <div id="test-12" class="test"> + <h2>12. Option: cutOutliers</h2> + <div class="epoch"></div> + <p> + <button class="playback">Play</button> | + <button class="cutOutliers" data-value="false">Keep Outliers</button> + <button class="cutOutliers" data-value="true">Cut Outliers</button> + </p> + </div> + <script> + $(function() { + var beta = window.beta = new BetaData(2, 5, 1), + data = beta.history(120); + + var chart = $('#test-12 .epoch').epoch({ + type: 'time.heatmap', + data: data, + axes: ['left', 'right'], + bucketRange: [0, 25], + buckets: 15, + windowSize: 100 + }); + + var interval = null, + pushPoint = function () { chart.push(beta.next()); }; + + $('#test-12 .playback').click(function(e) { + if (interval == null) { + $(e.target).text('Pause'); + pushPoint(); + interval = setInterval(pushPoint, 1000); + } + else { + $(e.target).text('Play'); + clearInterval(interval); + interval = null; + } + }); + + $('#test-12 .cutOutliers').click(function(e) { + var cutOutliers = $(e.target).attr('data-value') == "true" ? true : false; + chart.option('cutOutliers', cutOutliers); + }); + }); + </script> + + <div id="test-13" class="test"> + <h2>13. Option: opacity</h2> + <div class="epoch"></div> + <p> + <button class="playback">Play</button> | + <select class="opacity"> + <option value="root">√n</option> + <option value="linear" selected>n</option> + <option value="quadratic">n<sup>2</sup></option> + <option value="cubic">n<sup>3</sup></option> + <option value="quartic">n<sup>4</sup></option> + <option value="quintic">n<sup>5</sup></option> + </select> + </p> + </div> + <script> + $(function() { + var beta = window.beta = new BetaData(2, 5, 1), + data = beta.history(120); + + var chart = $('#test-13 .epoch').epoch({ + type: 'time.heatmap', + data: data, + axes: [], + buckets: 20, + windowSize: 100 + }); + + var interval = null, + pushPoint = function () { chart.push(beta.next()); }; + + $('#test-13 .playback').click(function(e) { + if (interval == null) { + $(e.target).text('Pause'); + pushPoint(); + interval = setInterval(pushPoint, 1000); + } + else { + $(e.target).text('Play'); + clearInterval(interval); + interval = null; + } + }); + + $('#test-13 select.opacity').on('change', function(e) { + var opacity = $('#test-13 select.opacity').val(); + chart.option('opacity', opacity); + }); + }); + </script> + + <div id="test-14" class="test"> + <h2>14. Show/Hide Layer</h2> + <div class="epoch"></div> + <p> + <button class="toggle" data-index="0">Toggle A</button> + <button class="toggle" data-index="1">Toggle B</button> | + <button class="playback">Play</button> + </p> + </div> + <script> + $(function() { + var normal = new NormalData(1), + normalData = normal.history(120)[0], + beta = new BetaData(2, 5, 1), + betaData = beta.history(120)[0], + data = [normalData, betaData], + interval = null; + + var chart = $('#test-14 .epoch').epoch({ + type: 'time.heatmap', + data: data, + windowSize: 120, + buckets: 20 + }); + + $('#test-14 .toggle').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.toggleLayer(index); + }); + + function pushNext() { + chart.push([normal.next()[0], beta.next()[0]]); + } + + $('#test-14 .playback').on('click', function(e) { + if (interval == null) { + pushNext(); + interval = setInterval(pushNext, 1000); + $(e.target).text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $(e.target).text('Play'); + } + }); + }); + </script> + </body> +</html> diff --git a/debian/missing-sources/epoch/tests/render/real-time/line.html b/debian/missing-sources/epoch/tests/render/real-time/line.html new file mode 100644 index 0000000..3346c98 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/line.html @@ -0,0 +1,596 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + + <script> + var nextTime = (function() { + var currentTime = parseInt(new Date().getTime() / 1000); + return function() { return currentTime++; } + })(); + </script> + </head> + <body> + <h1>Real-time Line Plot Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Real-time Line</p> + <ol> + <li><a href="#test-1">Single Series</a></li> + <li><a href="#test-2">Single Series Single Transition</a></li> + <li><a href="#test-3">Single Seires Stream</a></li> + <li><a href="#test-4">Multi Series</a></li> + <li><a href="#test-5">Multi Series Single Transition</a></li> + <li><a href="#test-6">Multi Seires Stream</a></li> + <li><a href="#test-7">Color Override</a></li> + <li><a href="#test-8">Categorical Colors</a></li> + <li><a href="#test-9">Show/Hide Layers</a></li> + <li><a href="#test-10">Show/Hide Layers</a></li> + <li><a href="#test-11">Multiaxes</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Single Series</h2> + <p> + Correctly render a single series plot of <code>y = cos(x) + 1</code> over the range <code>[0, 2π]</code>. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x) + 1, + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + $('#test-1 .epoch').epoch({ type: 'time.line', data: data }); + }); + </script> + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Single Series Single Transition</h2> + <p> + Correctly render a single series plot of <code>y = sin(x) + 1</code>. When the button is pressed push a new data point to the chart and correctly animate/render the transiton. + </p> + <div class="epoch"></div> + <p><button>Next</button></p> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40, + nextIndex = length; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.sin(x) + 1, + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + var chart = $('#test-2 .epoch').epoch({ + type: 'time.line', + data: data + }); + + $('#test-2 button').on('click', function(e) { + var x = nextIndex * 2 * Math.PI / length, + y = Math.sin(x) + 1, + time = nextTime(); + nextIndex++; + chart.push([{ time: time, y: y }]); + }); + }); + </script> + + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Single Seires Stream</h2> + <p>Correctly play / pause a single series stream of values from the plot <code>y = cos(x) + 1</code>.</p> + <div class="epoch"></div> + <p><button>Play</button></p> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40, + nextIndex = length, + playing = false, + interval = null; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x) + 1, + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + var chart = $('#test-3 .epoch').epoch({ + type: 'time.line', + data: data + }); + + var pushPoint = function() { + var x = nextIndex * 2 * Math.PI / length, + y = Math.cos(x) + 1, + time = nextTime(); + chart.push([{ time: time, y: y}]); + nextIndex++; + }; + + $('#test-3 button').on('click', function(e) { + if (playing) { + $(e.target).text('Play'); + clearInterval(interval); + } + else { + $(e.target).text('Pause'); + pushPoint(); + interval = setInterval(pushPoint, 1000); + } + playing = !playing; + }); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Multi Series</h2> + <p>Correctly render a multi series plot of the following functions:</p> + <ul> + <li><code>y = log(x+1)</code></li> + <li><code>y = 2*log(x+1)</code></li> + <li><code>y = 3*log(x+1)</code></li> + </ul> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i + 1, + time = nextTime(); + for (var j = 0; j < data.length; j++) { + data[j].values.push({time: time, y: (j+1) * Math.log(x)}); + } + } + + $('#test-4 .epoch').epoch({ + type: 'time.line', + data: data, + axes: ['left', 'right', 'bottom'] + }); + }); + </script> + + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Multi Series Single Transition</h2> + <p> + Correctly render a multi series plot of the following functions: + <ul> + <li><code>y = x</code></li> + <li><code>y = x<sup>1.25</sup></code></li> + <li><code>y = x<sup>1.5</sup></code></li> + </ul> + and correctly render/animate the transiton after pressing the "Next" button. + </p> + <div class="epoch"></div> + <p> + <button>Next</button> + </p> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40, + scale = 0.1, + nextIndex = length; + + for (var i = 0; i < length; i++) { + var x = i * scale, + time = nextTime(); + data[0].values.push({time: time, y: x}); + data[1].values.push({time: time, y: Math.pow(x, 1.25)}); + data[2].values.push({time: time, y: Math.pow(x, 1.5)}); + } + + var chart = $('#test-5 .epoch').epoch({ + type: 'time.line', + data: data, + axes: ['left', 'right', 'bottom'] + }); + + $('#test-5 button').on('click', function(e) { + var x = nextIndex * scale, + time = nextTime(); + nextIndex++; + chart.push([ + {time: time, y: x}, + {time: time, y: Math.pow(x, 1.25)}, + {time: time, y: Math.pow(x, 1.5)} + ]); + }); + }); + </script> + + <!-- Test 6 --> + <div id="test-6" class="test"> + <h2>6. Multi Seires Stream</h2> + <p> + Correctly play / pause a multi series stream of values over the following plots: + <ul> + <li><code>x</code></li> + <li><code>x * log(x)</code></li> + <li><code>x * log<sup>2</sup>(x)</code></li> + </ul> + </p> + <div class="epoch"></div> + <p><button>Play</button></p> + </div> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40, + nextIndex = length, + scale = 0.1, + playing = false, + interval = null; + + for (var i = 0; i < length; i++) { + var x = (i+1) * scale, + time = nextTime(); + data[0].values.push({time: time, y: x}); + data[1].values.push({time: time, y: x * Math.log(x)}); + data[2].values.push({time: time, y: x * Math.pow(Math.log(x), 2)}); + } + + var chart = $('#test-6 .epoch').epoch({ + type: 'time.line', + data: data, + axes: ['right', 'bottom'] + }); + + var pushPoint = function() { + var x = (nextIndex +1) * scale, + time = nextTime(); + chart.push([ + { time: time, y: x}, + { time: time, y: x * Math.log(x)}, + { time: time, y: x * Math.pow(Math.log(x), 2)} + ]); + nextIndex++; + }; + + $('#test-6 button').on('click', function(e) { + if (playing) { + $(e.target).text('Play'); + clearInterval(interval); + } + else { + $(e.target).text('Pause'); + interval = setInterval(pushPoint, 1000); + pushPoint(); + } + playing = !playing; + }); + }); + </script> + + <!-- Test 7 --> + <div id="test-7" class="test"> + <h2>7. Color Override</h2> + <p>The first layer should pink, the second green, and the third blue.</p> + <div id="test-7-plot" class="epoch"></div> + </div> + + <style> + #test-7-plot .a .line { stroke: pink; } + #test-7-plot .b .line { stroke: green; } + #test-7-plot .c .line { stroke: blue; } + </style> + + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i + 1, + time = nextTime(); + for (var j = 0; j < data.length; j++) { + data[j].values.push({time: time, y: (j+1) * Math.log(x)}); + } + } + + $('#test-7 .epoch').epoch({ + type: 'time.line', + data: data + }); + }); + </script> + + <!-- Test 8 --> + <div id="test-8" class="test"> + <h2>8. Categorical Colors</h2> + <p>Correctly render and switch between different categorical colors. Unlike with the basic charts this doesn't occur just by switching the class name on the containing element. The CSS query engine must be purge and the plot must be manually redrawn, like so:<code><pre> +Epoch.QueryCSS.purge(); +chart.draw(); +</pre></code> + </p> + + <div class="epoch category10"></div> + + <p> + <button data-class="category10">category10</button> + <button data-class="category20">category20</button> + <button data-class="category20b">category20b</button> + <button data-class="category20c">category20c</button> + </p> + </div> + + <script> + $(function() { + var data = [], + length = 41, + className = "category10", + layers = 8, + time = nextTime(); + + for (var i = 0; i < layers; i++) { + var layer = { label: String.fromCharCode(i+65), values: [] }; + for (var j = 0; j < length; j++) { + var x = j + 1, + y = Math.pow(x, i * 0.02); + layer.values.push({ time: time + j, y: y }); + } + data.push(layer); + } + + var chart = $('#test-8 .epoch').epoch({ type: 'time.line', data: data }); + + $('#test-8 button').on('click', function(e) { + $('#test-8 .epoch').removeClass(className); + className = $(e.target).attr('data-class'); + $('#test-8 .epoch').addClass(className); + Epoch.QueryCSS.purge(); + chart.draw(); + }); + }); + </script> + + <!-- Test 9 --> + <div id="test-9" class="test"> + <h2>9. Show/Hide Layers</h2> + <div class="epoch"></div> + <p> + <button class="toggle" data-index="0">Toggle A</button> + <button class="toggle" data-index="1">Toggle B</button> + <button class="toggle" data-index="2">Toggle C</button> | + <button class="playback">Play</button> + </p> + </div> + <script> + $(function() { + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 40, + nextIndex = length, + scale = 0.1, + playing = false, + interval = null; + + for (var i = 0; i < length; i++) { + var x = (i+1) * scale, + time = nextTime(); + data[0].values.push({time: time, y: x}); + data[1].values.push({time: time, y: x * Math.log(x)}); + data[2].values.push({time: time, y: x * Math.pow(Math.log(x), 2)}); + } + + var chart = $('#test-9 .epoch').epoch({ + type: 'time.line', + data: data, + axes: ['right', 'bottom'] + }); + + $('#test-9 .toggle').click(function(e) { + var index = parseInt($(e.target).attr('data-index')); + chart.toggleLayer(index); + }); + + var pushPoint = function() { + var x = (nextIndex +1) * scale, + time = nextTime(); + chart.push([ + { time: time, y: x}, + { time: time, y: x * Math.log(x)}, + { time: time, y: x * Math.pow(Math.log(x), 2)} + ]); + nextIndex++; + }; + + $('#test-9 .playback').on('click', function(e) { + if (playing) { + $(e.target).text('Play'); + clearInterval(interval); + } + else { + $(e.target).text('Pause'); + interval = setInterval(pushPoint, 1000); + pushPoint(); + } + playing = !playing; + }); + }); + </script> + + <!-- Test 10 --> + <div id="test-10" class="test"> + <h2>10. Fixed Range</h2> + <p> + Render a single series plot of <code>y = cos(x)</code>. Instead of automatically setting the range to <code>[-1, 1]</code>, the range is manually set to <code>[-2, 5]</code>. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [{ label: 'A', values: [] }], + length = 40; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x), + time = nextTime(); + data[0].values.push({time: time, y: y}); + } + + $('#test-10 .epoch').epoch({ + type: 'time.line', + axes: ['right', 'bottom'], + data: data, + range: [-2, 5] + }); + }); + </script> + + <!-- Test 11 --> + <div id="test-11" class="test"> + <h2>11. Multi-axes</h2> + <p> + Render a plot that uses independent axes ranges for the left and + right sides. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [ + { + label: 'A', + values: [], + range: [-5, 5] + }, + { + label: 'B', + values: [], + range: [0, 100] + } + ]; + var length = 40; + + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length, + y = Math.cos(x) + x / 4, + y2 = i * 100/40, + time = nextTime(); + data[0].values.push({time: time, y: y}); + data[1].values.push({time: time, y: y2}); + } + + $('#test-11 .epoch').epoch({ + type: 'time.line', + axes: ['left', 'right'], + data: data, + range: { + right: [-5, 5], + left: [0, 100] + } + }); + }); + </script> + + <!-- Test 12 --> + <div id="test-12" class="test"> + <h2>12. Multi-axes with Labeled Ranges</h2> + <p> + Render a plot that uses independent axes ranges for the left and + right sides that are associated to labels in the chart data. + </p> + <div class="epoch"></div> + </div> + + <script> + $(function() { + var data = [ + { + label: 'A', + values: [], + range: 'range-one' + }, + { + label: 'B', + values: [], + range: 'range-two' + }, + { + label: 'C', + values: [], + range: 'range-one' + } + ]; + var length = 50; + + for (var i = 0; i < length; i++) { + var y = Math.random() * 100; + var y2 = Math.random() * 2; + var y3 = Math.random() * 50 + 75 + var time = nextTime(); + data[0].values.push({time: time, y: y}); + data[1].values.push({time: time, y: y2}); + data[2].values.push({time: time, y: y3}); + } + + $('#test-12 .epoch').epoch({ + type: 'time.line', + axes: ['left', 'right'], + data: data, + range: { + right: 'range-one', + left: 'range-two' + } + }); + }); + </script> + </body> +</html> diff --git a/debian/missing-sources/epoch/tests/render/real-time/model.html b/debian/missing-sources/epoch/tests/render/real-time/model.html new file mode 100644 index 0000000..6f53963 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/model.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <script src="../js/data.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + <style> + body { background: #333; color: #d0d0d0; } + a:link, a:visited { color: white; color: white; } + + .epoch { + height: 220px; + } + + #sparkline { height: 50px; } + + </style> + </head> + <body class="epoch-theme-dark"> + <h1>Real-time Chart Model / Data Test</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Real-time Chart Model / Data Test</p> + + <p><button class="next">Next</button></p> + + <div id="gauge" class="epoch gauge-small"></div> + <div id="sparkline" class="epoch"></div> + <div id="area" class="epoch"></div> + <div id="bar" class="epoch"></div> + + <script> + $(function() { + var rnd = function() { return Math.random(); }; + + var data = []; + for (var j = 0; j < 3; j++) { + var layer = []; + for (var i = 0; i < 80; i++) { + layer.push(rnd()); + } + data.push(layer); + } + + // Setup the model + window.model = new Epoch.Model({ + dataFormat: { + name: 'array', + options: { startTime: (new Date().getTime() / 1000)|0 } + } + }); + model.setData(data); + + // Make the charts and associate them with the model + window.sparkline = $('#sparkline').epoch({ + type: 'time.line', + axes: ['left', 'right'], + model: model + }); + + window.area = $('#area').epoch({ + type: 'time.area', + axes: ['left', 'right', 'bottom'], + model: model + }); + + window.bar = $('#bar').epoch({ + type: 'time.bar', + axes: ['left', 'right', 'bottom'], + model: model + }); + + window.gauge = $('#gauge').epoch({ + type: 'time.gauge', + model: model + }) + + $('button.next').click(function(e) { + model.push([rnd(), rnd(), rnd()]); + }); + }) + </script> + </body> +</html>
\ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/real-time/options.html b/debian/missing-sources/epoch/tests/render/real-time/options.html new file mode 100644 index 0000000..a2da792 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/options.html @@ -0,0 +1,299 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <script src="../js/data.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + <style> + body { background: #333; color: #d0d0d0; } + a:link, a:visited { color: white; color: white; } + </style> + </head> + <body class="epoch-theme-dark"> + <h1>Real-time Chart Options and Events</h1> + <p class="breadcrumbs"><a href="../index.html">Epoch Chart Tests</a> » Real-time Options and Events</p> + <ol> + <li><a href="#test-1">Resize</a></li> + <li><a href="#test-2">Axes</a></li> + <li><a href="#test-3">Ticks</a></li> + <li><a href="#test-4">Tick Formats</a></li> + <li><a href="#test-5">Margins</a></li> + </ol> + + <!-- Test 1 --> + <div id="test-1" class="test"> + <h2>1. Resize</h2> + <p>Correctly Resize a Real-time Chart.</p> + <div class="epoch"></div> + <p> + <button class="playback">Play</button> | + <button class="size" data-index="0">Size: Small</button> + <button class="size" data-index="1">Size: Medium</button> + <button class="size" data-index="2">Size: Large</button> + </p> + </div> + <script> + $(function() { + var step = Math.PI / 30, + data = time().add(function(x) { return Math.cos(x) + 1; }), + interval = null; + + var sizes = [ + { width: 400, height: 100 }, + { width: 800, height: 150 }, + { width: $('#test-1 .epoch').width(), height: $('#test-1 .epoch').height() } + ]; + + var chart = $('#test-1 .epoch').epoch({ + type: 'time.line', + data: data.get([0, 2*Math.PI], step) + }) + + function pushPoint() { + chart.push(data.next(step)); + } + + $('#test-1 .playback').click(function(e) { + if (!interval) { + interval = setInterval(function() { pushPoint() }, 1000); + pushPoint(); + $('#test-1 .playback').text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $('#test-1 .playback').text('Play'); + } + }); + + $('#test-1 .size').click(function(e) { + var size = sizes[parseInt($(e.target).attr('data-index'))]; + chart.option('width', size.width); + chart.option('height', size.height); + }); + }); + </script> + + <!-- Test 2 --> + <div id="test-2" class="test"> + <h2>2. Axes</h2> + <div class="epoch"></div> + <div class="controls"> + <button class="playback">Play</button> | + <button class="axes" data-index="0">All</button> + <button class="axes" data-index="1">[left, right]</button> + <button class="axes" data-index="2">[top, bottom]</button> + <button class="axes" data-index="3">[left, bottom]</button> + <button class="axes" data-index="4">[top, right]</button> + <button class="axes" data-index="5">None</button> + </div> + </div> + <script> + $(function() { + var step = Math.PI / 30, + data = time().add(function(x) { return Math.sin(x) + 1; }), + interval = null; + + var axes = [ + ['top', 'left', 'bottom', 'right'], + ['left', 'right'], + ['top', 'bottom'], + ['left', 'bottom'], + ['top', 'right'], + [] + ]; + + var chart = $('#test-2 .epoch').epoch({ + type: 'time.line', + data: data.get([0, 2*Math.PI], step) + }) + + function pushPoint() { + chart.push(data.next(step)); + } + + $('#test-2 .playback').click(function(e) { + if (!interval) { + interval = setInterval(function() { pushPoint() }, 1000); + pushPoint(); + $('#test-2 .playback').text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $('#test-2 .playback').text('Play'); + } + }); + + $('#test-2 button.axes').click(function(e) { + chart.option('axes', axes[parseInt($(e.target).attr('data-index'))]); + }); + }); + </script> + + <!-- Test 3 --> + <div id="test-3" class="test"> + <h2>3. Ticks</h2> + <div class="epoch"></div> + <p> + <button class="playback">Play</button> | + <button class="ticks" data-index="0">Normal</button> + <button class="ticks" data-index="1">Many</button> + </p> + </div> + <script> + $(function() { + var step = Math.PI / 30, + data = time().add(function(x) { return Math.sqrt(x) * Math.sin(x) + 1; }), + interval = null; + + var ticks = [ + {time: 15, left: 5, right: 5}, + {time: 5, left: 15, right: 15} + ]; + + var chart = $('#test-3 .epoch').epoch({ + type: 'time.line', + data: data.get([0, 2*Math.PI], step), + axes: ['top', 'left', 'bottom', 'right'] + }) + + function pushPoint() { + chart.push(data.next(step)); + } + + $('#test-3 .playback').click(function(e) { + if (!interval) { + interval = setInterval(function() { pushPoint() }, 1000); + pushPoint(); + $('#test-3 .playback').text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $('#test-3 .playback').text('Play'); + } + }); + + $('#test-3 .ticks').click(function(e) { + chart.option('ticks', ticks[parseInt($(e.target).attr('data-index'))]); + }); + }); + </script> + + <!-- Test 4 --> + <div id="test-4" class="test"> + <h2>4. Tick Formats</h2> + <div class="epoch"></div> + <p> + <button class="playback">Play</button> | + <button class="tickFormats" data-index="0">Normal</button> + <button class="tickFormats" data-index="1">Different</button> + </p> + </div> + <script> + $(function() { + var step = Math.PI / 30, + data = time().add(function(x) { return Math.abs(Math.sin(x)); }), + interval = null; + + var tickFormats = [ + { + top: Epoch.Formats.seconds, + bottom: Epoch.Formats.seconds, + left: Epoch.Formats.si, + right: Epoch.Formats.si + }, + { + top: Epoch.Formats.si, + bottom: Epoch.Formats.si, + left: Epoch.Formats.percent, + right: Epoch.Formats.percent + } + ]; + + var chart = $('#test-4 .epoch').epoch({ + type: 'time.area', + data: data.get([0, 2*Math.PI], step), + axes: ['top', 'left', 'bottom', 'right'] + }) + + function pushPoint() { + chart.push(data.next(step)); + } + + $('#test-4 .playback').click(function(e) { + if (!interval) { + interval = setInterval(function() { pushPoint() }, 1000); + pushPoint(); + $('#test-4 .playback').text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $('#test-4 .playback').text('Play'); + } + }); + + $('#test-4 .tickFormats').click(function(e) { + chart.option('tickFormats', tickFormats[parseInt($(e.target).attr('data-index'))]); + }); + }); + </script> + + <!-- Test 5 --> + <div id="test-5" class="test"> + <h2>5. Margins</h2> + <div class="epoch"></div> + <p> + <button class="playback">Play</button> | + <button class="margins" data-index="0">None</button> + <button class="margins" data-index="1">Small</button> + <button class="margins" data-index="2">Big</button> + </p> + </div> + <script> + $(function() { + var step = Math.PI / 30, + data = time().add(function(x) { return 1 - Math.abs(Math.cos(x)); }), + interval = null; + + var margins = [ + { top: 6, bottom: 6, left: 6, right: 6 }, + { top: 20, bottom: 20, left: 20, right: 20 }, + { top: 100, bottom: 50, left: 50, right: 50 } + ]; + + var chart = $('#test-5 .epoch').epoch({ + type: 'time.area', + data: data.get([0, 2*Math.PI], step), + axes: [] + }) + + function pushPoint() { + chart.push(data.next(step)); + } + + $('#test-5 .playback').click(function(e) { + if (!interval) { + interval = setInterval(function() { pushPoint() }, 1000); + pushPoint(); + $('#test-5 .playback').text('Pause'); + } + else { + clearInterval(interval); + interval = null; + $('#test-5 .playback').text('Play'); + } + }); + + $('#test-5 .margins').click(function(e) { + chart.option('margins', margins[parseInt($(e.target).attr('data-index'))]); + }); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/themes/dark.html b/debian/missing-sources/epoch/tests/render/themes/dark.html new file mode 100644 index 0000000..265ad93 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/themes/dark.html @@ -0,0 +1,211 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + <style> + body { + background: #303030; + color: #d0d0d0; + } + + .epoch:after { + content:""; + display:table; + clear:both; + } + + .ref { + width: 120px; + height: 120px; + float: left; + margin: 2px; + color: #303030; + text-align: center; + line-height: 120px; + font-size: 31px; + } + </style> + + <script> + var data = [ + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] }, + { values: [] } + ]; + for (var i = 0; i < 60; i++) { + for (j = 0; j < data.length; j++) { + data[j].values.push({time: i, y: 0.05*i + 0.5}) + } + } + </script> + </head> + <body class="epoch-theme-dark"> + <h1>Dark Theme</h1> + + <h2>Category 10</h2> + <div class="epoch category10" data-refs-range="1,5"></div> + <div class="epoch category10" data-refs-range="6,10"></div> + + <br> + + <div id="bar-10" class="epoch category10" style="height: 300px"></div> + <script> + $(function() { + $('#bar-10').epoch({ type: 'time.bar', axes: ['left', 'right'], data: data.slice(0, 10) }); + }); + </script> + + + + <h2>Category 20</h2> + <div class="epoch category20" data-refs-range="1,4"></div> + <div class="epoch category20" data-refs-range="5,8"></div> + <div class="epoch category20" data-refs-range="9,12"></div> + <div class="epoch category20" data-refs-range="13,16"></div> + <div class="epoch category20" data-refs-range="17,20"></div> + + <br> + + <div id="bar-20" class="epoch category20" style="height: 300px"></div> + <script> + $(function() { + $('#bar-20').epoch({ type: 'time.bar', axes: ['left', 'right'], data: data }); + }); + </script> + + + <h2>Category 20b</h2> + <div class="epoch category20b" data-refs-range="1,4"></div> + <div class="epoch category20b" data-refs-range="5,8"></div> + <div class="epoch category20b" data-refs-range="9,12"></div> + <div class="epoch category20b" data-refs-range="13,16"></div> + <div class="epoch category20b" data-refs-range="17,20"></div> + + <br> + + <div id="bar-20b" class="epoch category20b" style="height: 300px"></div> + <script> + $(function() { + $('#bar-20b').epoch({ type: 'time.bar', axes: ['left', 'right'], data: data }); + }); + </script> + + + <h2>Category 20c</h2> + <div class="epoch category20c" data-refs-range="1,4"></div> + <div class="epoch category20c" data-refs-range="5,8"></div> + <div class="epoch category20c" data-refs-range="9,12"></div> + <div class="epoch category20c" data-refs-range="13,16"></div> + <div class="epoch category20c" data-refs-range="17,20"></div> + + <br> + + <div id="bar-20c" class="epoch category20c" style="height: 300px"></div> + <script> + $(function() { + $('#bar-20c').epoch({ type: 'time.bar', axes: ['left', 'right'], data: data }); + }); + </script> + + <h2>Chart Examples</h2> + + <div class="epoch gauge-medium"></div> + <script> + $(function() { + $('.gauge-medium').epoch({ type: 'time.gauge', value: 0.25 }); + }); + </script> + + <div id="pie" class="epoch" style="width: 300px; height: 300px;"></div> + <script> + $(function() { + var data = [], + className = 'category10'; + + for (var j = 0; j < 10; j++) { + data.push({label: String.fromCharCode(65+j), value: 10}); + } + + $('#pie').epoch({ + type: 'pie', + inner: 80, + data: data + }); + }); + </script> + + + <div id="bar" class="epoch" style="height: 220px"></div> + <script> + $(function(){ + var data = [ + {label: 'A', values: []}, + {label: 'B', values: []}, + {label: 'C', values: []} + ], + length = 10; + + for (var i = 0; i < length; i++) { + var x = i * 10 / length; + data[0].values.push({x: x, y: x}); + data[1].values.push({x: x, y: 2*x}); + data[2].values.push({x: x, y: 3*x}); + } + + $('#bar').epoch({ + type: 'bar', + data: data + }); + }); + </script> + + <div id="line" class="epoch" style="height: 220px"></div> + <script> + $(function() { + var data = [ + { label: 'A', values: [] }, + { label: 'B', values: [] } + ], + length = 128; + for (var i = 0; i < length; i++) { + var x = i * 2 * Math.PI / length; + data[0].values.push({x: x, y: x*Math.sin(x)}); + data[1].values.push({x: x, y: x*Math.cos(x)}); + } + $('#line').epoch({type: 'line', data: data}); + }); + </script> + + + <script> + $(function() { + $('div[data-refs-range]').each(function(i, el) { + var range = $(el).attr('data-refs-range').split(','); + for (var i = parseInt(range[0]); i <= parseInt(range[1]); i++) { + $(el).append('<div class="ref category'+i+'">'+i+'</div>') + } + }); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/themes/default.html b/debian/missing-sources/epoch/tests/render/themes/default.html new file mode 100644 index 0000000..9e8bd4e --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/themes/default.html @@ -0,0 +1,70 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="../css/tests.css"> + <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> + <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> + <script src="../../../dist/js/epoch.js"></script> + <link rel="stylesheet" type="text/css" href="../../../dist/css/epoch.css"> + <style> + + .epoch:after { + content:""; + display:table; + clear:both; + } + + .ref { + width: 120px; + height: 120px; + float: left; + margin: 2px; + color: white; + text-align: center; + line-height: 120px; + font-size: 31px; + } + </style> + + <script> + $(function() { + $('div[data-refs-range]').each(function(i, el) { + var range = $(el).attr('data-refs-range').split(','); + for (var i = parseInt(range[0]); i <= parseInt(range[1]); i++) { + $(el).append('<div class="ref category'+i+'">'+i+'</div>') + } + }); + }); + </script> + + </head> + <body> + <h1>Default Theme</h1> + + <h2>Category 10</h2> + <div class="epoch category10" data-refs-range="1,5"></div> + <div class="epoch category10" data-refs-range="6,10"></div> + + <h2>Category 20</h2> + <div class="epoch category20" data-refs-range="1,4"></div> + <div class="epoch category20" data-refs-range="5,8"></div> + <div class="epoch category20" data-refs-range="9,12"></div> + <div class="epoch category20" data-refs-range="13,16"></div> + <div class="epoch category20" data-refs-range="17,20"></div> + + <h2>Category 20b</h2> + <div class="epoch category20b" data-refs-range="1,4"></div> + <div class="epoch category20b" data-refs-range="5,8"></div> + <div class="epoch category20b" data-refs-range="9,12"></div> + <div class="epoch category20b" data-refs-range="13,16"></div> + <div class="epoch category20b" data-refs-range="17,20"></div> + + <h2>Category 20c</h2> + <div class="epoch category20c" data-refs-range="1,4"></div> + <div class="epoch category20c" data-refs-range="5,8"></div> + <div class="epoch category20c" data-refs-range="9,12"></div> + <div class="epoch category20c" data-refs-range="13,16"></div> + <div class="epoch category20c" data-refs-range="17,20"></div> + + + </body> +</html>
\ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/unit/core/charts.coffee b/debian/missing-sources/epoch/tests/unit/core/charts.coffee new file mode 100644 index 0000000..0f0bb9c --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/charts.coffee @@ -0,0 +1,573 @@ +sinon = require 'sinon' + +describe 'Epoch.Chart', -> + [defaultWidth, defaultHeight] = [320, 240] + + describe 'Base', -> + [testDivWidth, testDivHeight] = [800, 200] + [resizeDivWidth, resizeDivHeight] = [200, 200] + + before (done) -> + d3.select(doc.body).append('div').attr('id', 'testDiv').style + width: "#{testDivWidth}px" + height: "#{testDivHeight}px" + + d3.select(doc.body).append('div').attr('id', 'resizeDiv').style + width: "#{resizeDivWidth}px" + height: "#{resizeDivHeight}px" + + done() + + after (done) -> + d3.select('#testDiv').remove() + d3.select('#resizeDiv').remove() + done() + + describe 'constructor', -> + it 'should set default dimensions', -> + c = new Epoch.Chart.Base() + assert.equal c.width, defaultWidth, 'Did not set default width' + assert.equal c.height, defaultHeight, 'Did not set default height' + + it 'should allow dimensions to be set via options', -> + [width, height] = [500, 780] + c = new Epoch.Chart.Base({ width: width, height: height }) + assert.equal c.width, width, "Did not set width to #{width}" + assert.equal c.height, height, "Did not set height to #{height}" + + it 'should use the dimensions of the given element when applicable', -> + c = new Epoch.Chart.Base({ el: '#testDiv' }) + assert.equal c.width, testDivWidth, "Did not set width to that of the div" + assert.equal c.height, testDivHeight, "Did not set height to that of the div" + + it 'should set default data to an empty array', -> + c = new Epoch.Chart.Base() + assert.isArray c.data + assert.equal c.data.length, 0 + + it 'should set data when given as an option', -> + data = [ + {label: 'A', values: [{x: 0, y: 0}]}, + {label: 'B', values: [{x: 1, y: 1}]} + ] + c = new Epoch.Chart.Base({ data: data }) + assert.sameMembers(c.data, data) + + describe 'setData', -> + data = [ + {label: 'A', values: [{x: 10, y: 20}]}, + {label: 'B', values: [{x: 10, y: 20}]}, + {label: 'C', values: [{x: 10, y: 20}]} + ] + classNames = [ + ['layer', 'category1', 'a'], + ['layer', 'category2', 'b'], + ['layer', 'category3', 'c'] + ] + chart = null + + before (done) -> + (chart = new Epoch.Chart.Base()).setData(data) + done() + + it 'should set data correctly', -> + assert.sameMembers chart.data, data + for i in [0...data.length] + assert.equal chart.data[i].label, data[i].label + assert.equal chart.data[i].values[0], data[i].values[0] + + it 'should add the correct categories and class names', -> + for i in [0...data.length] + className = chart.data[i].className + for name in classNames[i] + assert (className.indexOf(name) > -1), "Missing class '#{name}'" + + describe 'draw', -> + it "should trigger the 'draw' event", (done) -> + errorCallback = -> + assert 'false', "The 'draw' event was never triggered" + done() + timeout = setTimeout(errorCallback, 1000) + chart = new Epoch.Chart.Base() + chart.on 'draw', -> + clearTimeout(timeout) + done() + chart.draw() + + describe 'update', -> + it 'should call draw by default', (done) -> + errorCallback = -> + assert false, "update did not call draw by default." + done() + timeout = setTimeout(errorCallback, 1000) + chart = new Epoch.Chart.Base() + chart.on 'draw', -> + clearTimeout(timeout) + done() + chart.update([]) + + it 'should not call draw when instructed', (done) -> + chart = new Epoch.Chart.Base() + chart.on 'draw', -> + assert false, "Update incorrectly called draw." + done() + chart.update([], false) + done() + + describe 'extent', -> + data = [ + {values: [ + {x: -1, y: 10}, + {x: 2, y: 20}, + {x: 4, y: 50}, + {x: 8, y: 9900} + ]}, + {values: [ + {x: 1, y: 170}, + {x: 7, y: -2380}, + {x: 19, y: 90}, + {x: 33, y: 17} + ]} + ] + + [xMin, xMax] = [-1, 33] + [yMin, yMax] = [-2380, 9900] + + chart = null + + before (done) -> + chart = new Epoch.Chart.Base({ data: data }) + done() + + it 'should find the correct extent given a y-comparitor', -> + [min, max] = chart.extent (d) -> d.y + assert.equal min, yMin, "Incorrect minimum y" + assert.equal max, yMax, "Incorrect maximum y" + + it 'should find the correct extent give an x-comparitor', -> + [min, max] = chart.extent (d) -> d.x + assert.equal min, xMin, "Incorrect minimum x" + assert.equal max, xMax, "Incorrect maximum x" + + describe 'option', -> + it 'should return all options for the chart when called with no arguments', -> + options = { a: 20, b: 30, c: { d: 40 } } + chart = new Epoch.Chart.Base options + assert.isObject chart.option() + assert.deepEqual chart.option(), options + + it 'should return a single value when given a key', -> + options = { a: 20, b: 30 } + chart = new Epoch.Chart.Base options + assert.equal chart.option('a'), options.a + assert.equal chart.option('b'), options.b + assert.isUndefined chart.option('c') + + it 'should return a deep value when given a hierarchical key', -> + options = + a: + b: 20 + c: + d: 30 + chart = new Epoch.Chart.Base options + assert.equal chart.option('a.b'), options.a.b + assert.equal chart.option('a.c.d'), options.a.c.d + + it 'should set an option given a string and a value', -> + chart = new Epoch.Chart.Base() + [key, value] = ['a', 'hello world'] + chart.option(key, value) + assert.equal chart.option(key), value + + it 'should set a deep value when given a hierarchical key', -> + chart = new Epoch.Chart.Base() + + map = + 'a.b': 'deep' + 'a.c.d': 'deeper' + 'b': 'shallow' + + for key, value of map + chart.option(key, value) + assert.equal chart.option(key), value + + it 'should set all options given an object', -> + original = { a: 20, b: { c: 30 } } + newOptions = { a: 15, d: { e: 10, f: 30 } } + chart = new Epoch.Chart.Base() + chart.option(newOptions) + assert.deepEqual chart.option(), newOptions + + it 'should trigger an event when an option is changed', (done) -> + [key, value] = ['a', 20] + eventName = "option:#{key}" + + errorCallback = -> + assert false, "Setting an option did not trigger the appropriate event: #{eventName}" + done() + timeout = setTimeout(errorCallback, 1000) + + chart = new Epoch.Chart.Base() + chart.on eventName, -> + clearTimeout(timeout) + done() + chart.option(key, value) + + it 'should resize the containing element when the width option is changed', -> + newWidth = resizeDivWidth + 20 + chart = new Epoch.Chart.Base({ el: '#resizeDiv' }) + chart.option('width', newWidth) + assert.equal d3.select('#resizeDiv').width(), newWidth + + it 'should resize the containing element when the height option is changed', -> + newHeight = resizeDivHeight + 20 + chart = new Epoch.Chart.Base({ el: '#resizeDiv' }) + chart.option('height', newHeight) + assert.equal d3.select('#resizeDiv').height(), newHeight + + describe '_getScaleDomain', -> + chart = null + + beforeEach -> + chart = new Epoch.Chart.Base + data: [ layerWithRange(-2030, 5050) ] + + it 'returns a given array', -> + assert.deepEqual(chart._getScaleDomain([0,1]), [0,1]) + + it 'returns @options.range if it is an array', -> + chart.options.range = [-100, 100] + assert.equal chart._getScaleDomain(), chart.options.range + + it 'returns @options.range.left if it is an array', -> + chart.options.range = {left: [-100, 100]} + assert.equal chart._getScaleDomain(), chart.options.range.left + + it 'returns @options.range.right if it is an array', -> + chart.options.range = {right: [-100, 100]} + assert.equal chart._getScaleDomain(), chart.options.range.right + + it 'returns the extent of the data', -> + assert.deepEqual chart._getScaleDomain(), chart.extent((d) -> d.y) + + describe 'with range grouped layers', -> + beforeEach -> + chart = new Epoch.Chart.Base + data: [ + layerWithRange(0, 10, 'left'), + layerWithRange(-5000, 5000, 'right'), + layerWithRange(-10, -5, 'left') + ] + + it 'returns the extent of the layers with the given range label', -> + assert.deepEqual chart._getScaleDomain('left'), [-10, 10] + + it 'returns the extent of the data if the label is invalid', -> + assert.deepEqual chart._getScaleDomain('foobar'), chart.extent((d) -> d.y) + + describe 'layers', -> + [chart, eventChart] = [null, null] + labels = ['A', 'B', 'C'] + data = [ + { label: 'A', data: [{x: 0, y: 0}] }, + { label: 'B', data: [{x: 1, y: 1}] }, + { label: 'C', data: [{x: 2, y: 2}] } + ] + data2 = [ + { label: 'A', data: [{x: 0, y: 0}] } + ] + + before (done) -> + chart = new Epoch.Chart.Base + el: doc.createElement('div') + data: data + eventChart = new Epoch.Chart.Base + el: doc.createElement('div') + data: data2 + done() + + describe '_findLayer', -> + it 'should find layers given a label', -> + for label in labels + layer = chart._findLayer(label) + assert.equal label, layer.label, "Could not find layer with label #{label}" + + it 'should find layers given an index', -> + for i in [0...data.length] + layer = chart._findLayer(i) + assert.equal labels[i], layer.label, "Could not find layer with index #{i}" + + it 'should return null if given an invalid label', -> + assert.isNull (chart._findLayer 'D') + assert.isNull (chart._findLayer 'not a thing') + + it 'should return null if given an index that is out of bounds', -> + assert.isNull (chart._findLayer -1) + assert.isNull (chart._findLayer 5) + + describe 'hideLayer', -> + it 'should hide a visible layer', -> + chart.hideLayer('A') + assert.isFalse chart._findLayer('A').visible + + it 'should keep a hidden layer hidden', -> + assert.isFalse chart._findLayer('A').visible + chart.hideLayer('A') + assert.isFalse chart._findLayer('A').visible + + it 'should trigger layer:hidden when a layer is hidden', (done) -> + errorCallback = -> + assert false, "layer:hidden was not triggered" + done() + timeout = setTimeout errorCallback, 1000 + eventChart.on 'layer:hidden', -> + clearTimeout timeout + done() + eventChart.hideLayer('A') + + describe 'showLayer', -> + it 'should have keep a visible layer visible', -> + assert.isTrue chart._findLayer('B').visible + chart.showLayer('B') + assert.isTrue chart._findLayer('B').visible + + it 'should make a hidden layer visible', -> + assert.isFalse chart._findLayer('A').visible + chart.showLayer('A') + assert.isTrue chart._findLayer('A').visible + + it 'should trigger layer:shown when a layer is shown', (done) -> + errorCallback = -> + assert false, "layer:shown was not triggered" + done() + timeout = setTimeout errorCallback, 1000 + eventChart.on 'layer:shown', -> + clearTimeout timeout + done() + eventChart.showLayer('A') + + describe 'toggleLayer', -> + it 'should hide a visible layer', -> + chart.hideLayer('A') + chart.toggleLayer('A') + assert.isTrue chart._findLayer('A').visible + + it 'should show a hidden layer', -> + chart.showLayer('B') + chart.toggleLayer('B') + assert.isFalse chart._findLayer('B').visible + + describe 'isLayerVisible', -> + it 'should report true if a layer is visible', -> + chart.showLayer('A') + assert.isTrue chart.isLayerVisible('A') + + it 'should report false if a layer is not visible', -> + chart.hideLayer('A') + assert.isFalse chart.isLayerVisible('B') + + describe 'getVisibleLayers', -> + it 'should only return visible layers', -> + chart.showLayer('A') + chart.showLayer('B') + chart.hideLayer('C') + visible = chart.getVisibleLayers() + assert.equal visible.length, 2 + assert.equal visible[0].label, 'A' + assert.equal visible[1].label, 'B' + + describe 'SVG', -> + [containerWidth, containerHeight] = [1000, 280] + container = null + + before (done) -> + container = doc.createElement('DIV') + container.id = 'svg-container' + doc.body.appendChild(container) + d3.select('#svg-container').style + 'width': "#{containerWidth}px" + 'height': "#{containerHeight}px" + done() + + after (done) -> + doc.body.removeChild(container) + done() + + describe 'constructor', -> + it 'should create a new SVG when not given an element', -> + chart = new Epoch.Chart.SVG() + assert.ok chart.svg, "SVG not created" + + it 'should set the default width and height of the SVG', -> + chart = new Epoch.Chart.SVG() + assert.equal chart.svg.attr('width'), defaultWidth, "Default width not set" + assert.equal chart.svg.attr('height'), defaultHeight, "Default height not set" + + it 'should set custom dimensions for the SVG via options', -> + [customWidth, customHeight] = [500, 600] + chart = new Epoch.Chart.SVG({ width: customWidth, height: customHeight }) + assert.equal chart.svg.attr('width'), customWidth, "Custom width not set" + assert.equal chart.svg.attr('height'), customHeight, "Custom height not set" + + it 'should set the container dimensions for the SVG', -> + chart = new Epoch.Chart.SVG({ el: '#svg-container' }) + assert.equal chart.svg.attr('width'), containerWidth + assert.equal chart.svg.attr('height'), containerHeight + + describe 'dimensionsChanged', -> + [width, height, chart] = [200, 100, null] + + before (done) -> + d3.select(doc.body).append('div').attr('id', 'svgResize').style + width: width + 'px' + height: height + 'px' + chart = new Epoch.Chart.SVG { el: '#svgResize' } + done() + + after (done) -> + d3.select('#svgResize').remove() + done() + + it 'should resize the SVG element when the width option is changed', -> + newWidth = width + 500 + chart.option 'width', newWidth + assert.equal chart.svg.attr('width'), newWidth + + it 'should resize the SVG element when the height option is changed', -> + newHeight = height + 500 + chart.option 'height', newHeight + assert.equal chart.svg.attr('height'), newHeight + + describe 'Canvas', -> + [containerWidth, containerHeight] = [1000, 280] + container = null + container_id = 'canvas-container' + containedChart = null + + before (done) -> + container = doc.createElement('DIV') + container.id = container_id + doc.body.appendChild(container) + d3.select('#' + container_id).style + 'width': "#{containerWidth}px" + 'height': "#{containerHeight}px" + + containedChart = new Epoch.Chart.Canvas + el: '#' + container_id + pixelRatio: 1 + + done() + + after (done) -> + doc.body.removeChild(container) + done() + + describe 'constructor', -> + it 'should correctly detect the pixelRatio', -> + chart = new Epoch.Chart.Canvas() + assert.equal chart.pixelRatio, 2 + + it 'should allow the pixelRatio to be explicitly overriden', -> + customPixelRatio = 4.2 + chart = new Epoch.Chart.Canvas({ pixelRatio: customPixelRatio }) + assert.equal chart.pixelRatio, customPixelRatio + + it 'should create a child canvas', -> + chart = new Epoch.Chart.Canvas() + assert.ok chart.canvas, "Did not create canvas" + assert.equal chart.canvas.node().tagName.toLowerCase(), 'canvas', 'Did not create a canvas node' + + it 'should append the child canvas to the containing element', -> + assert.equal containedChart.canvas.node().parentNode.id, container_id + + it 'should set the default dimensions for the canvas', -> + chart = new Epoch.Chart.Canvas({ pixelRatio: 1 }) + assert.equal chart.canvas.attr('width'), defaultWidth + assert.equal chart.canvas.attr('height'), defaultHeight + + it 'should allow custom dimensions for the canvas', -> + [customWidth, customHeight] = [999, 888] + chart = new Epoch.Chart.Canvas + width: customWidth + height: customHeight + pixelRatio: 1 + assert.equal chart.canvas.attr('width'), customWidth + assert.equal chart.canvas.attr('height'), customHeight + + it 'should set container dimensions for the canvas', -> + assert.equal containedChart.canvas.attr('width'), containerWidth + assert.equal containedChart.canvas.attr('height'), containerHeight + + it 'should fetch a graphics context from the canvas', -> + assert.ok containedChart.ctx, "Did not fetch graphics context from canvas" + + it 'should take pixel ratio into account when setting canvas dimension attributes', -> + pixelRatio = 3 + chart = new Epoch.Chart.Canvas({ pixelRatio: pixelRatio }) + assert.equal chart.canvas.attr('width'), defaultWidth * pixelRatio + assert.equal chart.canvas.attr('height'), defaultHeight * pixelRatio + + it 'should not take pixel ratio into account when setting canvas dimension styles', -> + chart = new Epoch.Chart.Canvas({ pixelRatio: 2 }) + assert.equal +chart.canvas.style('width').replace('px', ''), defaultWidth + assert.equal +chart.canvas.style('height').replace('px', ''), defaultHeight + + describe 'getWidth', -> + it 'should take pixel ratio into account', -> + pixelRatio = 2 + chart = new Epoch.Chart.Canvas({ pixelRatio: pixelRatio }) + assert.equal chart.getWidth(), pixelRatio * defaultWidth + + describe 'getHeight', -> + it 'should take pixel ratio into account', -> + pixelRatio = 2 + chart = new Epoch.Chart.Canvas({ pixelRatio: pixelRatio }) + assert.equal chart.getHeight(), pixelRatio * defaultHeight + + describe 'dimensionsChanged', -> + [width, height, chart, pixelRatio] = [200, 100, null, 2] + + before (done) -> + d3.select(doc.body).append('div').attr('id', 'canvasResize').style + width: width + 'px' + height: height + 'px' + chart = new Epoch.Chart.Canvas { el: '#canvasResize', pixelRatio: pixelRatio } + done() + + after (done) -> + d3.select('#canvasResize').remove() + done() + + it 'should resize the canvas element when the width option is changed', -> + newWidth = width + 500 + chart.option 'width', newWidth + assert.equal chart.canvas.attr('width'), pixelRatio * newWidth + assert.equal chart.canvas.width(), newWidth + + it 'should resize the canvas element when the height option is changed', -> + newHeight = height + 500 + chart.option 'height', newHeight + assert.equal chart.canvas.attr('height'), pixelRatio * newHeight + assert.equal chart.canvas.height(), newHeight + + describe 'redraw', -> + chart = null + drawSpy = null + purgeSpy = null + + before -> + chart = new Epoch.Chart.Canvas() + + beforeEach -> + drawSpy = sinon.spy chart, 'draw' + purgeSpy = sinon.spy Epoch.QueryCSS, 'purge' + + afterEach -> + chart.draw.restore() + Epoch.QueryCSS.purge.restore() + + it 'should purge QueryCSS cache and redraw the canvas based chart with new styles', -> + chart.redraw() + + assert drawSpy.calledOnce + assert purgeSpy.calledOnce diff --git a/debian/missing-sources/epoch/tests/unit/core/copy.coffee b/debian/missing-sources/epoch/tests/unit/core/copy.coffee new file mode 100644 index 0000000..2043241 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/copy.coffee @@ -0,0 +1,47 @@ +describe 'Epoch.Util', -> + describe 'copy', -> + it 'should correctly create a shallow copy', -> + object = + a: 20 + b: 'hello' + + copy = Epoch.Util.copy(object) + + assert.equal copy.a, object.a + assert.equal copy.b, object.b + + it 'should not recursively copy objects', -> + object = + a: + foo: 'bar' + + copy = Epoch.Util.copy(object) + object.a.foo = 'baz' + assert.equal object.a.foo, copy.a.foo + + describe 'defaults', -> + it 'should set default values when keys are missing', -> + options = {a: 'foo', b: 'bar'} + defaults = {c: 'baz'} + result = Epoch.Util.defaults(options, defaults) + assert.equal result.c, defaults.c + + it 'should not set default values when keys are present', -> + options = { a: 'foo', b: 'bar' } + defaults = { a: 'wow', b: 'neat' } + result = Epoch.Util.defaults(options, defaults) + assert.equal result.a, options.a + assert.equal result.b, options.b + + it 'should recursively set defaults from sub objects', -> + options = + a: + b: 'foo' + defaults = + a: + b: '' + c: 'bar' + result = Epoch.Util.defaults(options, defaults) + + assert.equal result.a.b, options.a.b + assert.equal result.a.c, defaults.a.c diff --git a/debian/missing-sources/epoch/tests/unit/core/css.coffee b/debian/missing-sources/epoch/tests/unit/core/css.coffee new file mode 100644 index 0000000..00cce68 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/css.coffee @@ -0,0 +1,85 @@ +describe 'Epoch.QueryCSS', -> + styleMap = + '#container rect': + 'fill': 'blue' + 'stroke': 'red' + 'stroke-width': '5px' + '#container rect.a': + 'fill': 'green' + 'stroke': 'yellow' + 'stroke-width': '1px' + 'rect#byid': + 'fill': 'purple' + 'stroke': '#94105A' + 'stroke-width': '15px' + 'body.alt-color rect#byid': + 'fill': '#abcdef1' + 'stroke': '#48419A' + 'stroke-width': '2em' + + [container, svg, styleTag] = [null, null, null] + + makeStyleSheet = -> + cssStatements = [] + for selector, rules of styleMap + cssStatements.push (selector + "{" + ("#{k}: #{v}" for k, v of rules).join(';') + "}") + css = cssStatements.join('\n') + styleTag = addStyleSheet(css) + + makeContainer = -> + container = d3.select(doc.body).append('div') + .attr('id', 'container') + svg = container.append('svg') + .attr('width', 10) + .attr('height', 10) + + assertStyles = (object, selector) -> + unless object? + assert(false, "Object contains no styles") + + unless (mapping = styleMap[selector])? + assert(false, "Could not find styles with selector: #{selector}") + + for key, value of mapping + assert.equal object[key], value, "Style mismatch on rule '#{key}'" + + before (done) -> + makeStyleSheet() + makeContainer() + done() + + after (done) -> + doc.head.removeChild(styleTag) + doc.body.removeChild(container.node()) + done() + + describe 'getStyles', -> + it 'should find styles for an svg element', -> + styles = Epoch.QueryCSS.getStyles('rect', container) + assertStyles styles, '#container rect' + + it 'should find styles using a specific class name', -> + styles = Epoch.QueryCSS.getStyles('rect.a', container) + assertStyles styles, '#container rect.a' + + it 'should find styles using an id', -> + styles = Epoch.QueryCSS.getStyles('rect#byid', container) + assertStyles styles, 'rect#byid' + + describe 'purge', -> + before (done) -> + d3.select(doc.body).attr('class', 'alt-color') + done() + + after (done) -> + d3.select(doc.body).attr('class', null) + done() + + it 'should find cached styles before a purge', -> + styles = Epoch.QueryCSS.getStyles('rect#byid', container) + assertStyles styles, 'rect#byid' + + it 'should find new styles after purging the cache', -> + Epoch.QueryCSS.purge() + styles = Epoch.QueryCSS.getStyles('rect#byid', container) + assertStyles styles, 'body.alt-color rect#byid' diff --git a/debian/missing-sources/epoch/tests/unit/core/d3.coffee b/debian/missing-sources/epoch/tests/unit/core/d3.coffee new file mode 100644 index 0000000..1b65c55 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/d3.coffee @@ -0,0 +1,40 @@ +describe 'd3.selection', -> + [width, height] = [345, 543, null] + [element, id] = [null, 'd3-element'] + + before (done) -> + element = doc.createElement('DIV') + element.id = id + doc.body.appendChild(element) + d3.select('#' + id).style + 'width': width + "px" + 'height': height + "px" + done() + + describe 'width', -> + it 'should return the width of an element', -> + assert.equal d3.select('#' + id).width(), width + + it 'should set the width of an element given a number', -> + widthNumber = 50 + d3.select('#'+id).width(widthNumber) + assert.equal d3.select('#'+id).width(), widthNumber + + it 'should set the width of an element given a css pixel length', -> + widthString = '500px' + d3.select('#'+id).width(widthString) + assert.equal d3.select('#'+id).width(), +widthString.replace('px', '') + + describe 'height', -> + it 'should return the height of an element', -> + assert.equal d3.select('#' + id).height(), height + + it 'should set the height of an element given a number', -> + heightNumber = 75 + d3.select('#'+id).height(heightNumber) + assert.equal d3.select('#'+id).height(), heightNumber + + it 'should set the height of an element given a css pixel length', -> + heightString = '343px' + d3.select('#'+id).height(heightString) + assert.equal d3.select('#'+id).height(), +heightString.replace('px', '') diff --git a/debian/missing-sources/epoch/tests/unit/core/events.coffee b/debian/missing-sources/epoch/tests/unit/core/events.coffee new file mode 100644 index 0000000..38d9356 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/events.coffee @@ -0,0 +1,111 @@ +describe 'Epoch.Events', -> + eventsObject = null + + before (done) -> + eventsObject = new Epoch.Events() + done() + + it 'should execute callbacks when events are triggered', (done) -> + errorCallback = -> + assert false, 'Event callback never executed' + done() + timeout = setTimeout errorCallback, 1000 + eventsObject.on 'event', -> + clearTimeout(timeout) + done() + eventsObject.trigger 'event' + + it 'should not execute callbacks that have been removed', (done) -> + errorCallback = -> + assert false, 'Event callback still executed' + done() + eventsObject.on 'example', errorCallback + eventsObject.off 'example', errorCallback + eventsObject.trigger 'example' + done() + + it 'should execute all callbacks associated with an event name', (done) -> + total = 4 + + errorCallback = -> + assert false, 'Not all callbacks were executed' + done() + timeout = setTimeout errorCallback, 1000 + + makeCallback = -> -> + total-- + if total == 0 + clearTimeout(timeout) + done() + eventsObject.on('multi', makeCallback()) for i in [0...total] + eventsObject.trigger 'multi' + + it 'should remove all callbacks when using .off([String])', (done) -> + makeCallback = -> -> + assert false, "A callback was still executed" + done() + eventsObject.on('multi2', makeCallback()) for i in [0...4] + eventsObject.off('multi2') + eventsObject.trigger('multi2') + setTimeout (-> done()), 200 + + it 'should execute methods on the object when using .on([String], [String])', (done) -> + errorCallback = -> + assert false, 'Trigger did not call the appropriate method.' + done() + timeout = setTimeout(errorCallback, 1000) + + eventsObject.method = -> + clearTimeout(timeout) + done() + + eventsObject.on 'method-event', 'method' + eventsObject.trigger 'method-event' + + it 'should register all events when executing .onAll([Object])', (done) -> + errorCallback = -> + assert false, 'Not all events were triggered.' + done() + timeout = setTimeout(errorCallback, 1000) + + eventNames = ['multi:a', 'multi:b', 'multi:c', 'multi:d'] + total = 0 + + eventCallback = -> + total += 1 + if total == eventNames.length + clearTimeout(timeout) + done() + + eventMap = {} + eventMap[name] = eventCallback for name in eventNames + + eventsObject.onAll(eventMap) + eventsObject.trigger(name) for name in eventNames + + + it 'should remove all events when executing .offAll([Array])', -> + eventCallback = -> + assert false, 'A removed callback was still triggered.' + + eventNames = ['multi-off:a', 'multi-off:b', 'multi-off:c', 'multi-off:d'] + eventMap = {} + eventMap[name] = eventCallback for name in eventNames + + eventsObject.onAll(eventMap) + eventsObject.offAll(eventNames) + eventsObject.trigger(name) for name in eventNames + + + it 'should remove specific event callbacks when executing .offAll([Object])', (done) -> + makeEventCallback = -> -> + assert false, 'A removed callback was still triggered.' + + eventNames = ['multi-off:a', 'multi-off:b', 'multi-off:c', 'multi-off:d'] + eventMap = {} + eventMap[name] = makeEventCallback() for name in eventNames + + eventsObject.onAll(eventMap) + eventsObject.offAll(eventMap) + eventsObject.trigger(name) for name in eventNames + setTimeout (-> done()), 200 diff --git a/debian/missing-sources/epoch/tests/unit/core/format.coffee b/debian/missing-sources/epoch/tests/unit/core/format.coffee new file mode 100644 index 0000000..a645b49 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/format.coffee @@ -0,0 +1,68 @@ +describe 'Epoch.Util', -> + describe 'formatSI', -> + it 'should produce the same number for integers < 1000', -> + number = 678 + assert.equal Epoch.Util.formatSI(number), number + + it 'should only set a fixed decimal for integers when instructed', -> + number = 20 + assert.equal Epoch.Util.formatSI(number), number + assert.equal Epoch.Util.formatSI(number, 1, true), "#{number}.0" + + it 'should set the appropriate number of fixed digits', -> + number = 3.1415 + for i in [1..5] + match = Epoch.Util.formatSI(number, i).split('.')[1] + assert.isNotNull match + assert.isString match + assert.equal match.length, i + + it 'should set the appropriate postfix based on the number\'s order of magnitude', -> + for i, postfix of ['K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] + number = Math.pow(10, ((i|0)+1)*3) + assert.equal Epoch.Util.formatSI(number), "1 #{postfix}" + + + describe 'formatBytes', -> + it 'should postfix numbers < 1024 with "B"', -> + number = 512 + assert.equal Epoch.Util.formatBytes(number), "#{number} B" + + it 'should only set a fixed decimal for integers when instructed', -> + assert.equal Epoch.Util.formatBytes(128), '128 B' + assert.equal Epoch.Util.formatBytes(128, 1, true), '128.0 B' + assert.equal Epoch.Util.formatBytes(1024), '1 KB' + assert.equal Epoch.Util.formatBytes(1024, 1, true), '1.0 KB' + + it 'should set the appropriate number of fixed digits', -> + number = 3.1415 + for i in [1..5] + fixed = Epoch.Util.formatBytes(number, i).replace(/\sB$/, '') + assert.isString fixed + match = fixed.split('.')[1] + assert.isNotNull match + assert.equal match.length, i + + it 'should set the appropriate postfix based on the number\'s order of magnitude', -> + for i, postfix of ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + number = Math.pow(1024, (i|0)+1) + regexp = new RegExp(" #{postfix}$") + assert.isNotNull Epoch.Util.formatBytes(number).match(regexp) + + +describe 'Epoch.Formats', -> + describe 'regular', -> + it 'should return what it was given', -> + assert.equal Epoch.Formats.regular(10), 10 + assert.equal Epoch.Formats.regular("hello"), "hello" + + describe 'percent', -> + it 'should return a percent given a number', -> + assert.equal Epoch.Formats.percent(0.1), '10.0%' + assert.equal Epoch.Formats.percent(0.5), '50.0%' + assert.equal Epoch.Formats.percent(1), '100.0%' + assert.equal Epoch.Formats.percent(23.245), '2324.5%' + + describe 'seconds', -> + it 'should return a well formatted date given a timestamp', -> + assert.match Epoch.Formats.seconds(1404385979), /\d{2}:\d{2}:\d{2} AM|PM/ diff --git a/debian/missing-sources/epoch/tests/unit/core/is.coffee b/debian/missing-sources/epoch/tests/unit/core/is.coffee new file mode 100644 index 0000000..af08eb2 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/is.coffee @@ -0,0 +1,88 @@ +describe 'Epoch.Util', -> + describe 'isArray', -> + it 'should return true if given an array', -> + assert.ok Epoch.isArray([]) + assert.ok Epoch.isArray([1, 2, 3]) + + it 'should return false if not given an array', -> + assert.notOk Epoch.isArray(2) + assert.notOk Epoch.isArray("hello") + assert.notOk Epoch.isArray({}) + + describe 'isObject', -> + it 'should return true if given an flat object', -> + assert.ok Epoch.isObject({}) + + it 'should return false if given a number object', -> + assert.notOk Epoch.isObject(new Number()) + + it 'should return false if given a non-object', -> + assert.notOk Epoch.isObject([]) + assert.notOk Epoch.isObject(2) + assert.notOk Epoch.isObject("string") + + describe 'isString', -> + it 'should return true if given a string', -> + assert.ok Epoch.isString("example") + assert.ok Epoch.isString(new String()) + + it 'should return false if given a non-string', -> + assert.notOk Epoch.isString(2) + assert.notOk Epoch.isString([]) + assert.notOk Epoch.isString({}) + + describe 'isFunction', -> + it 'should return true if given a function', -> + assert.ok Epoch.isFunction(->) + + it 'should return false if given a non-function', -> + assert.notOk Epoch.isFunction([]) + assert.notOk Epoch.isFunction({}) + assert.notOk Epoch.isFunction(42) + assert.notOk Epoch.isFunction("cool") + + describe 'isNumber', -> + it 'should return true if given a number', -> + assert.ok Epoch.isNumber(new Number()) + + it 'should return true if given an integer literal', -> + assert.ok Epoch.isNumber(1983) + + it 'should return true if given a floating point literal', -> + assert.ok Epoch.isNumber(3.1415) + + it 'should return false if given a non-number', -> + assert.notOk Epoch.isNumber(->) + assert.notOk Epoch.isNumber([]) + assert.notOk Epoch.isNumber({}) + assert.notOk Epoch.isNumber("nan") + + describe 'isElement', -> + it 'should return true given an html element', -> + p = doc.createElement('P') + assert.ok Epoch.isElement(p) + + it 'should return false given a non-element', -> + assert.notOk Epoch.isElement(1) + assert.notOk Epoch.isElement("1") + assert.notOk Epoch.isElement({}) + assert.notOk Epoch.isElement([]) + assert.notOk Epoch.isElement(->) + + describe 'isNonEmptyArray', -> + it 'should return true given a non-empty array', -> + assert.ok Epoch.isNonEmptyArray([1]) + assert.ok Epoch.isNonEmptyArray([1, 3]) + assert.ok Epoch.isNonEmptyArray(["foo", 4, "bar"]) + + it 'should return false given a non-array', -> + assert.notOk Epoch.isNonEmptyArray(2) + assert.notOk Epoch.isNonEmptyArray("five") + assert.notOk Epoch.isNonEmptyArray({}) + assert.notOk Epoch.isNonEmptyArray(->) + + it 'should return false given a null value', -> + assert.notOk Epoch.isNonEmptyArray(null) + + it 'should return false given an empty array', -> + assert.notOk Epoch.isNonEmptyArray([]) diff --git a/debian/missing-sources/epoch/tests/unit/core/util.coffee b/debian/missing-sources/epoch/tests/unit/core/util.coffee new file mode 100644 index 0000000..e02280c --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/util.coffee @@ -0,0 +1,92 @@ +describe 'Epoch.Util', -> + describe 'trim', -> + it 'should return null unless given a string', -> + assert.isNotNull Epoch.Util.trim('test string') + assert.isNull Epoch.Util.trim(34) + + it 'should trim leading and trailing whitespace', -> + assert.equal Epoch.Util.trim("\t\n\r indeed \n\t\t\r"), 'indeed' + + it 'should leave inner whitespace', -> + assert.equal Epoch.Util.trim('Hello world'), 'Hello world' + + describe 'dasherize', -> + it 'should dasherize regular strings', -> + assert.equal Epoch.Util.dasherize('Hello World'), 'hello-world' + + it 'should trim leading and trailing whitespace before dasherizing', -> + assert.equal Epoch.Util.dasherize(' Airieee is KewL '), 'airieee-is-kewl' + + describe 'domain', -> + testLayers = [ + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 40}] } + ] + + testLayers2 = [ + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 40}] }, + { values: [{x: 'D', y: 15}, {x: 'E', y: 30}, {x: 'F', y: 90}] } + ] + + it 'should find the correct domain of a set of keys and values', -> + xDomain = Epoch.Util.domain(testLayers, 'x') + assert.sameMembers xDomain, ['A', 'B', 'C'] + yDomain = Epoch.Util.domain(testLayers, 'y') + assert.sameMembers yDomain, [10, 20, 40] + + it 'should find all the values across multiple layers', -> + xDomain = Epoch.Util.domain(testLayers2, 'x') + assert.sameMembers xDomain, ['A', 'B', 'C', 'D', 'E', 'F'] + yDomain = Epoch.Util.domain(testLayers2, 'y') + assert.sameMembers yDomain, [10, 20, 40, 15, 30, 90] + + describe 'toRGBA', -> + it 'should produce the correct rgba style when given an rgba color style', -> + assert.equal Epoch.Util.toRGBA('rgba(1, 2, 3, 0.4)', 0.1), 'rgba(1,2,3,0.1)' + + it 'should produce the correct rgba style when given any rgb color style', -> + assert.equal Epoch.Util.toRGBA('black', 0.25), 'rgba(0,0,0,0.25)' + assert.equal Epoch.Util.toRGBA('#FF0000', 0.9), 'rgba(255,0,0,0.9)' + assert.equal Epoch.Util.toRGBA('rgb(10, 20, 40)', 0.99), 'rgba(10,20,40,0.99)' + + describe 'getComputedStyle', -> + overrideStyles = + 'width': '320px' + 'height': '240px' + 'background-color': 'blue' + + [style, div] = [null, null] + + before (done) -> + style = addStyleSheet('#get-style-div { padding-left: 30px; background: green }') + div = doc.createElement('div') + div.id = 'get-style-div' + doc.body.appendChild(div) + d3.select('#get-style-div').style(overrideStyles) + done() + + after (done) -> + doc.body.removeChild(div) + doc.head.removeChild(style) + done() + + it 'should find <style> styles', -> + styles = Epoch.Util.getComputedStyle(div) + assert.equal styles['padding-left'], '30px' + + it 'should find overriden styles', -> + styles = Epoch.Util.getComputedStyle(div) + for k, v of overrideStyles + assert.equal styles[k], v, "ComputedStyles['#{k}'] should be '#{v}'" + + describe 'flatten', -> + it 'should flatten a given multi-array', -> + multiarray = [[1, 2], 3, [4, 5, 6, [7]]] + expected = [1, 2, 3, 4, 5, 6, [7]] + assert.deepEqual Epoch.Util.flatten(multiarray), expected + + it 'should throw if given a non-array', -> + assert.throws (-> Epoch.Util.flatten null), /only accepts arrays/ + assert.throws (-> Epoch.Util.flatten 1), /only accepts arrays/ + assert.throws (-> Epoch.Util.flatten {}), /only accepts arrays/ + assert.throws (-> Epoch.Util.flatten 'hellooo'), /only accepts arrays/ + assert.throws (-> Epoch.Util.flatten new Error()), /only accepts arrays/ diff --git a/debian/missing-sources/epoch/tests/unit/data/array_format.coffee b/debian/missing-sources/epoch/tests/unit/data/array_format.coffee new file mode 100644 index 0000000..a043a1e --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/data/array_format.coffee @@ -0,0 +1,106 @@ +describe 'Epoch.Data.Format.array', -> + startTime = 1000 + + it 'should format flat arrays', -> + expected = [ {values: [{x: 0, y: 1}, {x: 1, y: 2}, {x: 2, y: 3}]} ] + assert.data expected, Epoch.Data.Format.array([1, 2, 3]) + + it 'should format multi-dimensional arrays', -> + expected = [ + { values: [{x: 0, y: 1}, {x: 1, y: 2}]}, + { values: [{x: 0, y: 3}, {x: 1, y: 4}]} + ] + assert.data expected, Epoch.Data.Format.array([[1, 2], [3, 4]]) + + it 'should respect the x option', -> + expected = [{values: [{x: 1, y: 1}, {x: 2, y: 2}]}] + result = Epoch.Data.Format.array [1, 2], {x: (d, i) -> i+1} + assert.data expected, result + + it 'should respect the y option', -> + expected = [{values: [{x: 0, y: 2}, {x: 1, y: 4}]}] + result = Epoch.Data.Format.array [1, 2], {y: (d) -> d*2} + assert.data expected, result + + it 'should format pie chart data with flat arrays', -> + input = [20, 30, 40] + expected = ({value: v} for v in input) + result = Epoch.Data.Format.array input, {type: 'pie'} + assert.equal expected.length, result.length, "Result did not have the expected number of layers" + for i in [0...expected.length] + assert.equal expected[i].value, result[i].value, "Result #{i} did not have the epected value" + + it 'should not format pie chart data with multi-dimensional arrays', -> + assert.equal Epoch.Data.Format.array([[1], [2]], {type: 'pie'}).length, 0 + + it 'should format real-time plot data with flat arrays', -> + input = [1, 2, 3] + expected = [{ values: ({time: startTime+parseInt(i), y: v} for i,v of input) }] + result = Epoch.Data.Format.array(input, {type: 'time.line', startTime: startTime}) + assert.timeData expected, result + + it 'should format real-time plot data with multi-dimensional arrays', -> + input = [[1, 2], [3, 4]] + expected = [] + for layer in input + expected.push {values: ({time: startTime+parseInt(i), y: v} for i, v of layer)} + result = Epoch.Data.Format.array(input, {type: 'time.line', startTime: startTime}) + assert.timeData expected, result + + it 'should format heatmap data with flat arrays', -> + input = [{'1': 1, '2': 2}, {'3': 3, '4': 4}] + expected = [{values: ({time: startTime+parseInt(i), histogram: h} for i, h of input)}] + result = Epoch.Data.Format.array(input, {type: 'time.heatmap', startTime: startTime}) + assert.data expected, result, ['time', 'heatmap'] + + it 'should format heatmap data with multi-dimensional arrays', -> + input = [ + [{'1': 1, '2': 2}, {'3': 3, '4': 4}], + [{'5': 5, '6': 6}, {'7': 7, '8': 8}] + ] + expected = [ + { values: ({time: startTime+parseInt(i), histogram: h} for i, h of input[0]) }, + { values: ({time: startTime+parseInt(i), histogram: h} for i, h of input[1]) }, + ] + result = Epoch.Data.Format.array(input, {type: 'time.heatmap', startTime: startTime}) + assert.data expected, result, ['time', 'heatmap'] + + it 'should correctly apply labels if the labels option is present', -> + labels = ['alpha', 'beta'] + result = Epoch.Data.Format.array [[1], [2]], {labels: labels} + for i in [0...labels.length] + assert.equal labels[i], result[i].label + + it 'should correctly apply labels if the autoLabels option is set', -> + labels = ['A', 'B', 'C'] + result = Epoch.Data.Format.array [[1], [2], [3]], {autoLabels: true} + for i in [0...labels.length] + assert.equal labels[i], result[i].label + + it 'should prefer the labels option to the autoLabels option if both are set', -> + labels = ['alpha', 'beta'] + result = Epoch.Data.Format.array [[1], [2]], {labels: labels, autoLabels: true} + for i in [0...labels.length] + assert.equal labels[i], result[i].label + + it 'should produce single series entries correctly', -> + result = Epoch.Data.Format.array.entry(2) + assert.isArray result + assert.equal 1, result.length + assert.isObject result[0] + assert.equal 0, result[0].x + assert.equal 2, result[0].y + + it 'should produce multi-series entries correctly', -> + expected = [ + { x: 0, y: 1 }, + { x: 0, y: 2 }, + { x: 0, y: 3 } + ] + result = Epoch.Data.Format.array.entry([1, 2, 3]) + assert.isArray result + assert.equal 3, result.length + for i in [0...expected.length] + assert.isObject result[i] + assert.equal expected[i].x, result[i].x + assert.equal expected[i].y, result[i].y diff --git a/debian/missing-sources/epoch/tests/unit/data/chart.coffee b/debian/missing-sources/epoch/tests/unit/data/chart.coffee new file mode 100644 index 0000000..ed115a1 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/data/chart.coffee @@ -0,0 +1,121 @@ + +describe 'Epoch.Chart.options', -> + it 'should set the type option to "area" for basic area charts', -> + assert.equal new Epoch.Chart.Area().options.type, 'area' + + it 'should set the type option to "bar" for basic bar charts', -> + assert.equal new Epoch.Chart.Bar().options.type, 'bar' + + it 'should set the type option to "histogram" for basic histogram charts', -> + assert.equal new Epoch.Chart.Histogram().options.type, 'histogram' + + it 'should set the type option to "line" for basic line charts', -> + assert.equal new Epoch.Chart.Line().options.type, 'line' + + it 'should set the type option to "pie" for basic pie charts', -> + assert.equal new Epoch.Chart.Pie().options.type, 'pie' + + it 'should set the type option to "scatter" for basic scatter charts', -> + assert.equal new Epoch.Chart.Scatter().options.type, 'scatter' + + it 'should set the type option to "time.area" for real-time area charts', -> + assert.equal new Epoch.Time.Area().options.type, 'time.area' + + it 'should set the type option to "time.bar" for real-time bar charts', -> + assert.equal new Epoch.Time.Bar().options.type, 'time.bar' + + it 'should set the type option to "time.gauge" for real-time gauge charts', -> + assert.equal new Epoch.Time.Gauge().options.type, 'time.gauge' + + it 'should set the type option to "time.heatmap" for real-time heatmap charts', -> + assert.equal new Epoch.Time.Heatmap().options.type, 'time.heatmap' + + it 'should set the type option to "time.line" for real-time line charts', -> + assert.equal new Epoch.Time.Line().options.type, 'time.line' + +describe 'Epoch.Chart._formatData', -> + assertBasicData = (klassName, type) -> + data = [1, 2, 3, 4] + expected = Epoch.data 'array', data, {type: type} + chart = new Epoch.Chart[klassName] + data: data + dataFormat: 'array' + assert.data expected, chart.data + + assertTimeData = (klassName, type) -> + data = [1, 2, 3, 4] + expected = Epoch.data 'array', data, {type: type, time: (d, i) -> parseInt(i)} + chart = new Epoch.Time[klassName] + data: data + dataFormat: + name: 'array' + options: { time: (d, i) -> parseInt(i) } + assert.timeData expected, chart.data + + it 'should correctly detect and format array type data', -> + data = [1, 2, 3] + expected = Epoch.data 'array', data + chart = new Epoch.Chart.Base + data: data + dataFormat: 'array' + assert.data expected, chart.data + + it 'should correctly detect and format tuple type data', -> + data = [[1, 1], [2, 4], [3, 78]] + expected = Epoch.data 'tuple', data + chart = new Epoch.Chart.Base + data: data + dataFormat: 'tuple' + assert.data expected, chart.data + + it 'should correctly detect and format keyvalue type data', -> + data = [ {a: 20, b: 30, x: 10}, {a: 40, b: 50, x: 20} ] + expected = Epoch.data 'keyvalue', data, ['a', 'b'], { x: (d) -> d.x } + chart = new Epoch.Chart.Base + data: data + dataFormat: + name: 'keyvalue' + arguments: [['a', 'b']] + options: { x: (d, i) -> d.x } + assert.data expected, chart.data + + it 'should correctly format area chart data', -> + assertBasicData 'Area', 'area' + + it 'should correctly format bar chart data', -> + assertBasicData 'Bar', 'bar' + + it 'should correctly format line data', -> + assertBasicData 'Line', 'line' + + it 'should correctly format scatter data', -> + assertBasicData 'Scatter', 'scatter' + + it 'should correctly format pie data', -> + data = [1, 2, 3] + expected = data.map (d) -> {value: d} + result = (new Epoch.Chart.Pie(data: data, dataFormat: 'array')).data + for i in [0...expected.length] + assert.equal expected[i].value, result[i].value + + it 'should correctly format histogram data', -> + data = (parseInt(Math.random() * 100) for i in [0...100]) + format = Epoch.data('array', data, { type: 'histogram' }) + expected = (new Epoch.Chart.Histogram())._prepareData(format) + chart = new Epoch.Chart.Histogram({ data: data, dataFormat: 'array' }) + assert.data expected, chart.data + + it 'should correctly format real-time area data', -> + assertTimeData 'Area', 'time.area' + + it 'should correctly format real-time bar data', -> + assertTimeData 'Bar', 'time.bar' + + it 'should correctly format real-time heatmap data', -> + assertTimeData 'Heatmap', 'time.heatmap' + + it 'should correctly format real-time line data', -> + assertTimeData 'Line', 'time.line' + + + diff --git a/debian/missing-sources/epoch/tests/unit/data/keyvalue_format.coffee b/debian/missing-sources/epoch/tests/unit/data/keyvalue_format.coffee new file mode 100644 index 0000000..1ac5fcc --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/data/keyvalue_format.coffee @@ -0,0 +1,101 @@ +describe 'Epoch.Data.Format.keyvalue', -> + + data = [ + { x: 1, a: 20, b: 30, c: 40, hist: 10, time: 0 }, + { x: 2, a: 45, b: 83, c: 8, hist: 11, time: 1 }, + { x: 3, a: 17, b: 72, c: 54, hist: 12, time: 2 }, + { x: 4, a: 99, b: 19, c: 39, hist: 13, time: 3 } + ] + + it 'should format a single series for basic plots', -> + expected = [{ values: (data.map (d, i) -> { x: parseInt(i), y: d.a }) }] + result = Epoch.Data.Format.keyvalue(data, ['a']) + assert.data expected, result + + it 'should format multiple series for basic plots', -> + expected = [ + { values: (data.map (d, i) -> {x: parseInt(i), y: d.a }) }, + { values: (data.map (d, i) -> {x: parseInt(i), y: d.b }) }, + { values: (data.map (d, i) -> {x: parseInt(i), y: d.c }) } + ] + result = Epoch.Data.Format.keyvalue(data, ['a', 'b', 'c']) + assert.data expected, result + + it 'should format a single series for real-time plots', -> + expected = [ + { values: (data.map (d, i) -> { time: d.time, y: d.a }) } + ] + result = Epoch.Data.Format.keyvalue(data, ['a'], { type: 'time.line', time: (d) -> d.time }) + assert.data expected, result + + it 'should format multiple series for real-time plots', -> + expected = [ + { values: (data.map (d, i) -> { time: d.time, y: d.a }) } + { values: (data.map (d, i) -> { time: d.time, y: d.b }) } + { values: (data.map (d, i) -> { time: d.time, y: d.c }) } + ] + result = Epoch.Data.Format.keyvalue(data, ['a', 'b', 'c'], { type: 'time.line', time: (d) -> d.time }) + assert.data expected, result + + it 'should correctly format heatmap data', -> + expected = [ + { values: (data.map (d, i) -> {time: d.time, histogram: d.hist }) } + ] + result = Epoch.Data.Format.keyvalue(data, ['hist'], {type: 'time.heatmap', time: ((d) -> d.time) }) + assert.data expected, result + + it 'should return an empty set for type time.gauge and type pie', -> + assert.equal Epoch.Data.Format.keyvalue(data, ['a'], {type: 'pie'}).length, 0 + assert.equal Epoch.Data.Format.keyvalue(data, ['a'], {type: 'time.gauge'}).length, 0 + + it 'should respect the x option', -> + expected = [{ values: (data.map (d, i) -> {x: d.x, y: d.a }) }] + result = Epoch.Data.Format.keyvalue(data, ['a'], {x: (d) -> d.x}) + assert.data expected, result + + it 'should respect the y option', -> + expected = [{ values: (data.map (d, i) -> {x: parseInt(i), y: d.a + 2 }) }] + result = Epoch.Data.Format.keyvalue(data, ['a'], {y: (d) -> d + 2}) + assert.data expected, result + + it 'should apply key name labels by default', -> + labels = ['a', 'b', 'c', 'hist'] + layers = Epoch.Data.Format.keyvalue(data, ['a', 'b', 'c', 'hist']) + for i in [0...labels.length] + assert.equal labels[i], layers[i].label + + it 'should override key name labels with given labels', -> + labels = ['x', 'y', 'z'] + layers = Epoch.Data.Format.keyvalue(data, ['a', 'b', 'c'], {labels: labels}) + for i in [0...labels.length] + assert.equal labels[i], layers[i].label + + it 'should apply automatic labels only when labels are not given and key labels are off', -> + labels = ['A', 'B'] + layers = Epoch.Data.Format.keyvalue(data, ['a', 'b'], {keyLabels: false, autoLabels: true}) + for i in [0...labels.length] + assert.equal labels[i], layers[i].label + + it 'should produce single series entries correctly', -> + input = data[0] + keys = ['a'] + expected = [{x: 0, y: input.a}] + result = Epoch.Data.Format.keyvalue.entry(input, keys) + assert.isArray result + assert.equal 1, result.length + assert.isObject result[0] + assert.equal 0, result[0].x + assert.equal input.a, result[0].y + + it 'should produce multi-series entries correctly', -> + input = data[1] + keys = ['a', 'b', 'c'] + options = {x: 'x'} + expected = ({x: input.x, y: input[key]} for key in keys) + result = Epoch.Data.Format.keyvalue.entry(input, keys, options) + assert.isArray result + assert.equal expected.length, result.length + for i in [0...expected.length] + assert.isObject result[i] + assert.equal expected[i].x, result[i].x + assert.equal expected[i].y, result[i].y diff --git a/debian/missing-sources/epoch/tests/unit/data/tuple_format.coffee b/debian/missing-sources/epoch/tests/unit/data/tuple_format.coffee new file mode 100644 index 0000000..a55f559 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/data/tuple_format.coffee @@ -0,0 +1,76 @@ +describe 'Epoch.Data.Format.tuple', -> + it 'should format flat tuple arrays', -> + input = [[1, 2], [3, 4], [5, 6]] + expected = [{values: input.map((d) -> {x: d[0], y: d[1]})}] + result = Epoch.Data.Format.tuple(input) + assert.data expected, result + + it 'should format nested layers of tuple arrays', -> + input = [ + [ [1, 2], [3, 4] ], + [ [5, 6], [7, 8] ] + ] + expected = input.map (series) -> + {values: series.map((d) -> {x: d[0], y: d[1]})} + result = Epoch.Data.Format.tuple(input) + assert.data expected, result + + it 'should respect the x option', -> + input = [[1, 2], [3, 4], [5, 6]] + expected = [{values: input.map((d, i) -> {x: i, y: d[1]})}] + result = Epoch.Data.Format.tuple(input, {x: (d, i) -> i}) + assert.data expected, result + + it 'should respect the y option', -> + input = [[1, 2], [3, 4], [5, 6]] + expected = [{values: input.map((d, i) -> {x: d[0], y: i})}] + result = Epoch.Data.Format.tuple(input, {y: (d, i) -> i}) + assert.data expected, result + + it 'should format flat tuples of real-time data', -> + input = [[1, 2], [3, 4], [5, 6]] + expected = [{values: input.map((d) -> {time: d[0], y: d[1]})}] + result = Epoch.Data.Format.tuple(input, {type: 'time.line'}) + assert.data expected, result + + it 'should format nested layers of real-time tuple data', -> + input = [ + [ [1, 2], [3, 4] ], + [ [5, 6], [7, 8] ] + ] + expected = input.map (series) -> + {values: series.map((d) -> {time: d[0], y: d[1]})} + result = Epoch.Data.Format.tuple(input, {type: 'time.line'}) + assert.data expected, result + + it 'should respect the time option', -> + input = [[1, 2], [3, 4], [5, 6]] + expected = [{values: input.map((d, i) -> {time: i, y: d[1]})}] + result = Epoch.Data.Format.tuple(input, {type: 'time.line', time: (d, i) -> i}) + assert.data expected, result + + it 'should ignore heatmap, pie, and gauge charts', -> + input = [[1, 2], [3, 4], [5, 6]] + assert.equal 0, Epoch.Data.Format.tuple(input, {type: 'time.heatmap'}).length + assert.equal 0, Epoch.Data.Format.tuple(input, {type: 'time.gauge'}).length + assert.equal 0, Epoch.Data.Format.tuple(input, {type: 'pie'}).length + + it 'should produce single series entries correctly', -> + input = [5, 6] + result = Epoch.Data.Format.tuple.entry(input) + assert.isArray result + assert.equal 1, result.length + assert.isObject result[0] + assert.equal input[0], result[0].x + assert.equal input[1], result[0].y + + it 'should produce multi-series entries correctly', -> + input = [[5, -10], [4, 8], [2, 3]] + expected = ({x: d[0], y: d[1]} for d in input) + result = Epoch.Data.Format.tuple.entry(input) + assert.isArray result + assert.equal expected.length, result.length + for i in [0...expected.length] + assert.isObject result[i] + assert.equal expected[i].x, result[i].x + assert.equal expected[i].y, result[i].y diff --git a/debian/missing-sources/epoch/tests/unit/init.coffee b/debian/missing-sources/epoch/tests/unit/init.coffee new file mode 100644 index 0000000..aa8edd2 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/init.coffee @@ -0,0 +1,55 @@ +process.env.TZ = "America/Los_Angeles" + +jsdom = require('jsdom') +global.assert = require('chai').assert +url = require('url') + +html = "<html><head></head><body></body></html>" + +exec = require('child_process').exec +exec 'pwd', (err, out) -> console.log out + +before (done) -> + jsdom.env + html: html + scripts: ["http://d3js.org/d3.v3.min.js", "./dist/js/epoch.js"] + done: (errors, window) -> + global.Epoch = window.Epoch + # Override get context to use a test context by default + global.Epoch.Util.getContext = -> new window.Epoch.TestContext() + global.d3 = window.d3 + global.doc = window.document + # Set this to "retina" so we can test canvas based charts + window.devicePixelRatio = 2 + done() + +global.addStyleSheet = (css) -> + head = doc.head + style = doc.createElement('style') + style.type = 'text/css' + style.appendChild(doc.createTextNode(css)) + head.appendChild(style) + style + +global.layerWithRange = (min, max, range) -> + layer = { values: [{time: 0, y: min}, {time: 1, y: max}] } + layer.range = range if range? + layer + +# +# Helper assertion methods for data format testing +# +assert.data = (expected, result, checkAttributes) -> + checkAttributes ?= ['x', 'y'] + assert.equal expected.length, result.length + for i, layer of expected + resultLayer = result[i] + msg = "Result layer #{i} does not have expected number of values." + assert.equal layer.values.length, resultLayer.values.length, msg + for j in [0...layer.values.length] + for k in checkAttributes + msg = "Layer #{i} data point #{j} does not have the expected value for key #{k}" + assert.equal layer.values[j][k], resultLayer.values[j][k], msg + +assert.timeData = (expected, result) -> + assert.data(expected, result, ['time', 'y']) diff --git a/debian/missing-sources/epoch/tests/unit/time.coffee b/debian/missing-sources/epoch/tests/unit/time.coffee new file mode 100644 index 0000000..48593a2 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/time.coffee @@ -0,0 +1,67 @@ +sinon = require 'sinon' + +describe 'Epoch.Time.Plot', -> + chart = null + + beforeEach -> + chart = new Epoch.Time.Plot(data: [layerWithRange(0, 100)]) + + describe 'y', -> + scaleDomain = [-524, 2324] + beforeEach -> sinon.stub(chart, '_getScaleDomain').returns(scaleDomain) + afterEach -> chart._getScaleDomain.restore() + + it 'should get the scale domain from the given domain', -> + y = chart.y('a') + assert.ok chart._getScaleDomain.calledWith('a') + assert.deepEqual y.domain(), scaleDomain + + describe 'ySvg', -> + scaleDomain = [3004, 10000000] + beforeEach -> sinon.stub(chart, '_getScaleDomain').returns(scaleDomain) + afterEach -> chart._getScaleDomain.restore() + + it 'should get the scale domain from the given domain', -> + y = chart.ySvg('a') + assert.ok chart._getScaleDomain.calledWith('a') + assert.deepEqual y.domain(), scaleDomain + + describe 'ySvgLeft', -> + beforeEach -> sinon.spy(chart, 'ySvg') + afterEach -> chart.ySvg.restore() + + it 'should use the left range when present', -> + chart.options.range = { left: 'apples' } + chart.ySvgLeft() + assert.ok chart.ySvg.calledWith('apples') + + it 'should not use the left range when missing', -> + chart.ySvgLeft() + assert.ok chart.ySvg.calledOnce + + describe 'ySvgRight', -> + beforeEach -> sinon.spy(chart, 'ySvg') + afterEach -> chart.ySvg.restore() + + it 'should use the right range when present', -> + chart.options.range = { right: 'oranges' } + chart.ySvgRight() + assert.ok chart.ySvg.calledWith('oranges') + + it 'should not use the right range when missing', -> + chart.ySvgRight() + assert.ok chart.ySvg.calledOnce + + describe 'leftAxis', -> + beforeEach -> sinon.spy chart, 'ySvgLeft' + afterEach -> chart.ySvgLeft.restore() + it 'uses the left svg scale', -> + chart.leftAxis() + assert.ok chart.ySvgLeft.calledOnce + + describe 'rightAxis', -> + beforeEach -> sinon.spy chart, 'ySvgRight' + afterEach -> chart.ySvgRight.restore() + it 'uses the right svg scale', -> + chart.rightAxis() + assert.ok chart.ySvgRight.calledOnce diff --git a/debian/missing-sources/epoch/tests/unit/time/line.coffee b/debian/missing-sources/epoch/tests/unit/time/line.coffee new file mode 100644 index 0000000..fa6c205 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/time/line.coffee @@ -0,0 +1,15 @@ +sinon = require 'sinon' + +describe 'Epoch.Time.Line', -> + chart = null + beforeEach -> + chart = new Epoch.Time.Line + data: [{ range: 'foo', values: [{time: 0, y: 10}, {time: 1, y: 30}] }] + + describe 'draw', -> + beforeEach -> sinon.spy chart, 'y' + afterEach -> chart.y.restore() + + it 'should provide the layer\'s range to the y scale', -> + chart.draw() + assert.ok chart.y.calledWith('foo') |