зеркало из https://github.com/microsoft/scenepic.git
Point release adding additional graph features. (#47)
New Features: - Can specify text alignment and size for graph labels and values - Can add vertical rules to separate out horizontal regions of the graph - `save_html` can now separate out the script and library components (for easier testing of new Typescript library features) Signed-off-by: Matthew A Johnson <matjoh@microsoft.com>
This commit is contained in:
Родитель
8c39973578
Коммит
575ed31f3e
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,6 +1,16 @@
|
|||
# Changelog
|
||||
|
||||
## [2022-08-26 - Version 1.0.12](https://github.com/microsoft/scenepic/releases/tag/v1.0.13)
|
||||
## [2022-09-07 - Version 1.0.14](https://github.com/microsoft/scenepic/releases/tag/v1.0.14)
|
||||
Point release adding additional graph features.
|
||||
|
||||
New Features:
|
||||
- Can specify text alignment and size for graph labels and values
|
||||
- Can add vertical rules to separate out horizontal regions of the graph
|
||||
- `save_html` can now separate out the script and library components
|
||||
(for easier testing of new Typescript library features)
|
||||
|
||||
|
||||
## [2022-08-26 - Version 1.0.13](https://github.com/microsoft/scenepic/releases/tag/v1.0.13)
|
||||
Point release adding stub files for all Python classes. (#17)
|
||||
|
||||
New Features:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
Point release adding stub files for all Python classes. (#17)
|
||||
Point release adding additional graph features.
|
||||
|
||||
New Features:
|
||||
- Stub files for all classes properly expose the docstrings in VS Code and other
|
||||
editors, thus making development in Python considerably easier.
|
||||
- Can specify text alignment and size for graph labels and values
|
||||
- Can add vertical rules to separate out horizontal regions of the graph
|
||||
- `save_html` can now separate out the script and library components
|
||||
(for easier testing of new Typescript library features)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.0.13
|
||||
1.0.14
|
50
bundle.js
50
bundle.js
|
@ -13,30 +13,30 @@ let mapFile = "dist/scenepic.min.js.map"
|
|||
version = fs.readFileSync("VERSION")
|
||||
stream.write("// " + version + "\n")
|
||||
|
||||
let debug = false
|
||||
let b = browserify({
|
||||
// set this to true to produce a source map
|
||||
// for debugging the TypeScript library
|
||||
debug: false
|
||||
debug: debug
|
||||
})
|
||||
.add([
|
||||
"tssrc/Canvas2D.ts",
|
||||
"tssrc/Canvas3D.ts",
|
||||
"tssrc/CanvasBase.ts",
|
||||
"tssrc/CSSStyles.ts",
|
||||
"tssrc/DropDownMenu.ts",
|
||||
"tssrc/Mesh.ts",
|
||||
"tssrc/Misc.ts",
|
||||
"tssrc/Shaders.ts",
|
||||
"tssrc/SPScene.ts",
|
||||
"tssrc/TextPanel.ts",
|
||||
"tssrc/WebGLMeshBuffers.ts",
|
||||
"tssrc/ScenePic.ts",
|
||||
"tssrc/VertexBuffers.ts"
|
||||
])
|
||||
.plugin(tsify)
|
||||
.bundle()
|
||||
.pipe(minify())
|
||||
// commenting this line (i.e. disabling exorcist) can make debugging easier
|
||||
// by inlining the sourcemaps
|
||||
.pipe(exorcist(mapFile))
|
||||
.pipe(stream)
|
||||
.add([
|
||||
"tssrc/Canvas2D.ts",
|
||||
"tssrc/Canvas3D.ts",
|
||||
"tssrc/CanvasBase.ts",
|
||||
"tssrc/CSSStyles.ts",
|
||||
"tssrc/DropDownMenu.ts",
|
||||
"tssrc/Mesh.ts",
|
||||
"tssrc/Misc.ts",
|
||||
"tssrc/Shaders.ts",
|
||||
"tssrc/SPScene.ts",
|
||||
"tssrc/TextPanel.ts",
|
||||
"tssrc/WebGLMeshBuffers.ts",
|
||||
"tssrc/ScenePic.ts",
|
||||
"tssrc/VertexBuffers.ts"
|
||||
])
|
||||
.plugin(tsify)
|
||||
.bundle()
|
||||
|
||||
if (!debug) {
|
||||
b = b.pipe(minify()).pipe(exorcist(mapFile))
|
||||
}
|
||||
|
||||
b.pipe(stream)
|
||||
|
|
|
@ -22,7 +22,7 @@ copyright = "2021, Microsoft"
|
|||
author = "ScenePic Team"
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = "1.0.13"
|
||||
release = "1.0.14"
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "scenepic",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.14",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "scenepic",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.14",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"npm": "^8.18.0"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_comment0": "This file is used by npm to specify the project and npm dependencies",
|
||||
"name": "scenepic",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.14",
|
||||
"description": "3D Visualization Made Easy",
|
||||
"author": "ScenePic Team",
|
||||
"license": "MIT",
|
||||
|
|
|
@ -2,8 +2,6 @@ numpy>=1.16.2
|
|||
pillow>=5.2.0
|
||||
scipy>=1.4.1
|
||||
opencv-python
|
||||
azure-storage-blob>=12.0.0
|
||||
azure-common
|
||||
nbstripout
|
||||
pytest-emoji
|
||||
pytest-md
|
||||
|
|
|
@ -1675,17 +1675,6 @@ HTML_FORMULA_FORMAT = png
|
|||
|
||||
FORMULA_FONTSIZE = 10
|
||||
|
||||
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
|
||||
# generated for formulas are transparent PNGs. Transparent PNGs are not
|
||||
# supported properly for IE 6.0, but are supported on all modern browsers.
|
||||
#
|
||||
# Note that when changing this option you need to delete any form_*.png files in
|
||||
# the HTML output directory before the changes have effect.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
FORMULA_TRANSPARENT = YES
|
||||
|
||||
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
|
||||
# to create new LaTeX commands to be used in formulas as building blocks. See
|
||||
# the section "Including formulas" for details.
|
||||
|
@ -2400,23 +2389,6 @@ HAVE_DOT = NO
|
|||
|
||||
DOT_NUM_THREADS = 0
|
||||
|
||||
# When you want a differently looking font in the dot files that doxygen
|
||||
# generates you can specify the font name using DOT_FONTNAME. You need to make
|
||||
# sure dot is able to find the font, which can be done by putting it in a
|
||||
# standard location or by setting the DOTFONTPATH environment variable or by
|
||||
# setting DOT_FONTPATH to the directory containing the font.
|
||||
# The default value is: Helvetica.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_FONTNAME = Helvetica
|
||||
|
||||
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
|
||||
# dot graphs.
|
||||
# Minimum value: 4, maximum value: 24, default value: 10.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_FONTSIZE = 10
|
||||
|
||||
# By default doxygen will tell dot to use the default font as specified with
|
||||
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
|
||||
# the path where dot can find it using this tag.
|
||||
|
@ -2661,18 +2633,6 @@ DOT_GRAPH_MAX_NODES = 50
|
|||
|
||||
MAX_DOT_GRAPH_DEPTH = 0
|
||||
|
||||
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
|
||||
# background. This is disabled by default, because dot on Windows does not seem
|
||||
# to support this out of the box.
|
||||
#
|
||||
# Warning: Depending on the platform used, enabling this option may lead to
|
||||
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
|
||||
# read).
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_TRANSPARENT = NO
|
||||
|
||||
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
|
||||
# files in one run (i.e. multiple -o and -T options on the command line). This
|
||||
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
|
||||
|
|
|
@ -58,6 +58,35 @@ namespace scenepic
|
|||
double left;
|
||||
};
|
||||
|
||||
/** Represents a vertical line in a sparkline graph. */
|
||||
struct VerticalRule
|
||||
{
|
||||
/** Constructor.
|
||||
* \param frame the frame at which to add the line
|
||||
* \param color the color of the line
|
||||
* \param line_width the width of the line in pixels
|
||||
*/
|
||||
VerticalRule(
|
||||
std::int64_t frame,
|
||||
const Color& color = scenepic::Colors::Black,
|
||||
float line_width = 1.0f);
|
||||
|
||||
/** Returns a string representation of the margin */
|
||||
std::string to_string() const;
|
||||
|
||||
/** Convert this object into ScenePic json.
|
||||
* \return a json value
|
||||
*/
|
||||
JsonValue to_json() const;
|
||||
|
||||
/** The frame at which to add the line. */
|
||||
std::int64_t frame;
|
||||
/** The color of the line. */
|
||||
Color color;
|
||||
/** The width of the line in pixels. */
|
||||
float line_width;
|
||||
};
|
||||
|
||||
/** A unique identifier for the canvas */
|
||||
const std::string& canvas_id() const;
|
||||
|
||||
|
@ -66,12 +95,14 @@ namespace scenepic
|
|||
* \param values a vector of measured values, one per frame
|
||||
* \param line_color the color of the line (and its labels)
|
||||
* \param line_width the width of the line
|
||||
* \param vertical_rules vertical rules to draw on the sparkline
|
||||
*/
|
||||
void add_sparkline(
|
||||
const std::string& name,
|
||||
const std::vector<float>& values,
|
||||
const scenepic::Color& line_color = scenepic::Colors::Black,
|
||||
float line_width = 1.0f);
|
||||
float line_width = 1.0f,
|
||||
const std::vector<VerticalRule>& vertical_rules = {});
|
||||
|
||||
/** Return a JSON string representing the object */
|
||||
std::string to_string() const;
|
||||
|
@ -93,6 +124,26 @@ namespace scenepic
|
|||
/** Set the outside margin of the graph */
|
||||
Graph& margin(const Margin& margin);
|
||||
|
||||
/** How to align the sparkline label (one of 'left', 'right', 'top',
|
||||
* 'bottom', or 'none')
|
||||
*/
|
||||
const std::string& name_align() const;
|
||||
|
||||
/** How to align the sparkline label (one of 'left', 'right', 'top',
|
||||
* 'bottom', or 'none')
|
||||
*/
|
||||
Graph& name_align(const std::string& name_align);
|
||||
|
||||
/** How to align the sparkline value (one of 'left', 'right', 'top',
|
||||
* 'bottom', or 'none')
|
||||
*/
|
||||
const std::string& value_align() const;
|
||||
|
||||
/** How to align the sparkline value (one of 'left', 'right', 'top',
|
||||
* 'bottom', or 'none')
|
||||
*/
|
||||
Graph& value_align(const std::string& value_align);
|
||||
|
||||
/** The font family used for the graph labels */
|
||||
const std::string& font_family() const;
|
||||
|
||||
|
@ -100,10 +151,16 @@ namespace scenepic
|
|||
Graph& font_family(const std::string& font_family);
|
||||
|
||||
/** The size of the graph labels in pixels */
|
||||
float text_size() const;
|
||||
float name_size() const;
|
||||
|
||||
/** Set the size of the graph labels in pixels */
|
||||
Graph& text_size(float text_size);
|
||||
Graph& name_size(float name_size);
|
||||
|
||||
/** The size of the graph labels in pixels */
|
||||
float value_size() const;
|
||||
|
||||
/** Set the size of the graph labels in pixels */
|
||||
Graph& value_size(float value_size);
|
||||
|
||||
/** The unique ID of the media file associated with this canvas.
|
||||
* This file will be used to drive playback, i.e. frames will be displayed
|
||||
|
@ -122,7 +179,8 @@ namespace scenepic
|
|||
const std::string& name,
|
||||
const ValueBuffer& values,
|
||||
const Color& color,
|
||||
float line_width);
|
||||
float line_width,
|
||||
const std::vector<VerticalRule>& vertical_rules);
|
||||
|
||||
std::string to_string() const;
|
||||
JsonValue to_json() const;
|
||||
|
@ -132,6 +190,7 @@ namespace scenepic
|
|||
ValueBuffer m_values;
|
||||
Color m_color;
|
||||
float m_line_width;
|
||||
std::vector<VerticalRule> m_vertical_rules;
|
||||
};
|
||||
|
||||
friend class Scene;
|
||||
|
@ -147,7 +206,10 @@ namespace scenepic
|
|||
Color m_background_color;
|
||||
Margin m_margin;
|
||||
std::string m_font_family;
|
||||
float m_text_size;
|
||||
std::string m_name_align;
|
||||
std::string m_value_align;
|
||||
float m_name_size;
|
||||
float m_value_size;
|
||||
};
|
||||
} // namespace scenepic
|
||||
|
||||
|
|
|
@ -144,13 +144,18 @@ namespace scenepic
|
|||
* automatically populated if not provided).
|
||||
* \param width the width in pixels of the Canvas on the HTML page
|
||||
* \param height the height in pixels of the Canvas on the HTML page
|
||||
* \param name_align How to align the sparkline label (one of 'left',
|
||||
* 'right', 'top', or 'bottom')
|
||||
* \param value_align How to align the sparkline value (one of 'left',
|
||||
* 'right', 'top', or 'bottom')
|
||||
* \param html_id optional id of an HTML element to use as this Canvas's
|
||||
* parent in the HTML DOM (otherwise simply appended to
|
||||
* document).
|
||||
* \param background_color optional background color for the canvas
|
||||
* \param margin the outer margin of the graph
|
||||
* \param font_family the font family used for the graph labels
|
||||
* \param text_size the text size in pixels used for the graph labels
|
||||
* \param name_size the text size in pixels used for the graph labels
|
||||
* \param value_size the text size in pixels used for the graph values
|
||||
* \param media_id optional ID of a media file to attach to the canvas.
|
||||
* This file will be used to drive playback, i.e. frames
|
||||
* will be displayed in time with the playback of the
|
||||
|
@ -161,11 +166,14 @@ namespace scenepic
|
|||
const std::string& canvas_id = "",
|
||||
double width = 400,
|
||||
double height = 400,
|
||||
const std::string& name_align = "left",
|
||||
const std::string& value_align = "right",
|
||||
const std::string& html_id = "",
|
||||
const Color& background_color = Colors::White,
|
||||
const Graph::Margin& margin = Graph::Margin(),
|
||||
const std::string& font_family = "sans-serif",
|
||||
float text_size = 12.0,
|
||||
float name_size = 12.0,
|
||||
float value_size = 12.0,
|
||||
const std::string& media_id = "");
|
||||
|
||||
/** Create a new AudioTrack and add directly to the Scene.
|
||||
|
@ -480,17 +488,23 @@ namespace scenepic
|
|||
/** Returns a breakdown of the number of bytes used by each command type. */
|
||||
std::map<std::string, std::size_t> measure_command_size() const;
|
||||
|
||||
/** Save the scene as a self-contained html file with no dependencies.
|
||||
/** Save the scene as a html file.
|
||||
* \param path the path to the file on disk
|
||||
* \param title the HTML title
|
||||
* \param head_html the raw HTML to place in the HEAD tag
|
||||
* \param body_html the raw HTML to place in the BODY tag
|
||||
* \param script_path desired relative path for the script. Empty string
|
||||
* indicates to embed the script in the HTML page.
|
||||
* \param library_path desired relative path for the library. Empty string
|
||||
* indicates to embed the library in the HTML page.
|
||||
*/
|
||||
void save_as_html(
|
||||
const std::string& path,
|
||||
const std::string& title = "ScenePic",
|
||||
const std::string& head_html = "",
|
||||
const std::string& body_html = "");
|
||||
const std::string& body_html = "",
|
||||
const std::string& script_path = "",
|
||||
const std::string& library_path = "");
|
||||
|
||||
/** Whether the script has been cleared */
|
||||
bool script_cleared() const;
|
||||
|
|
|
@ -34,8 +34,13 @@ namespace scenepic
|
|||
const std::string& name,
|
||||
const ValueBuffer& values,
|
||||
const Color& color,
|
||||
float line_width)
|
||||
: m_name(name), m_values(values), m_color(color), m_line_width(line_width)
|
||||
float line_width,
|
||||
const std::vector<VerticalRule>& vertical_rules)
|
||||
: m_name(name),
|
||||
m_values(values),
|
||||
m_color(color),
|
||||
m_line_width(line_width),
|
||||
m_vertical_rules(vertical_rules)
|
||||
{}
|
||||
|
||||
JsonValue Graph::Sparkline::to_json() const
|
||||
|
@ -46,9 +51,48 @@ namespace scenepic
|
|||
command["Name"] = m_name;
|
||||
command["StrokeStyle"] = m_color.to_html_hex();
|
||||
command["LineWidth"] = m_line_width;
|
||||
|
||||
JsonValue vertical_rules;
|
||||
for (const auto& vertical_rule : m_vertical_rules)
|
||||
{
|
||||
vertical_rules.append(vertical_rule.to_json());
|
||||
}
|
||||
|
||||
if (m_vertical_rules.size() == 0)
|
||||
{
|
||||
// this will force it to be an array
|
||||
vertical_rules.resize(0);
|
||||
}
|
||||
|
||||
command["VerticalRules"] = vertical_rules;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
std::string Graph::Sparkline::to_string() const
|
||||
{
|
||||
return this->to_json().to_string();
|
||||
}
|
||||
|
||||
Graph::VerticalRule::VerticalRule(
|
||||
std::int64_t frame, const Color& color, float line_width)
|
||||
: frame(frame), color(color), line_width(line_width)
|
||||
{}
|
||||
|
||||
JsonValue Graph::VerticalRule::to_json() const
|
||||
{
|
||||
JsonValue command;
|
||||
command["FrameIndex"] = frame;
|
||||
command["StrokeStyle"] = color.to_html_hex();
|
||||
command["LineWidth"] = line_width;
|
||||
return command;
|
||||
}
|
||||
|
||||
std::string Graph::VerticalRule::to_string() const
|
||||
{
|
||||
return this->to_json().to_string();
|
||||
}
|
||||
|
||||
const std::string& Graph::canvas_id() const
|
||||
{
|
||||
return m_canvas_id;
|
||||
|
@ -58,11 +102,13 @@ namespace scenepic
|
|||
const std::string& name,
|
||||
const std::vector<float>& values,
|
||||
const scenepic::Color& color,
|
||||
float line_width)
|
||||
float line_width,
|
||||
const std::vector<VerticalRule>& vertical_rules)
|
||||
{
|
||||
ValueBuffer value_buffer(values.size(), 1);
|
||||
std::copy(values.begin(), values.end(), value_buffer.data());
|
||||
m_sparklines.emplace_back(name, value_buffer, color, line_width);
|
||||
m_sparklines.emplace_back(
|
||||
name, value_buffer, color, line_width, vertical_rules);
|
||||
}
|
||||
|
||||
Graph::Graph(const std::string& canvas_id) : m_canvas_id(canvas_id) {}
|
||||
|
@ -91,7 +137,10 @@ namespace scenepic
|
|||
JsonValue text;
|
||||
text["CommandType"] = "SetTextStyle";
|
||||
text["FontFamily"] = m_font_family;
|
||||
text["SizeInPixels"] = m_text_size;
|
||||
text["NameSizeInPixels"] = m_name_size;
|
||||
text["ValueSizeInPixels"] = m_value_size;
|
||||
text["NameAlign"] = m_name_align;
|
||||
text["ValueAlign"] = m_value_align;
|
||||
canvas_commands.append(text);
|
||||
|
||||
for (const auto& sparkline : m_sparklines)
|
||||
|
@ -135,6 +184,28 @@ namespace scenepic
|
|||
return *this;
|
||||
}
|
||||
|
||||
const std::string& Graph::name_align() const
|
||||
{
|
||||
return m_name_align;
|
||||
}
|
||||
|
||||
Graph& Graph::name_align(const std::string& name_align)
|
||||
{
|
||||
m_name_align = name_align;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::string& Graph::value_align() const
|
||||
{
|
||||
return m_value_align;
|
||||
}
|
||||
|
||||
Graph& Graph::value_align(const std::string& value_align)
|
||||
{
|
||||
m_value_align = value_align;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::string& Graph::font_family() const
|
||||
{
|
||||
return m_font_family;
|
||||
|
@ -146,14 +217,25 @@ namespace scenepic
|
|||
return *this;
|
||||
}
|
||||
|
||||
float Graph::text_size() const
|
||||
float Graph::name_size() const
|
||||
{
|
||||
return m_text_size;
|
||||
return m_name_size;
|
||||
}
|
||||
|
||||
Graph& Graph::text_size(float text_size)
|
||||
Graph& Graph::name_size(float name_size)
|
||||
{
|
||||
m_text_size = text_size;
|
||||
m_name_size = name_size;
|
||||
return *this;
|
||||
}
|
||||
|
||||
float Graph::value_size() const
|
||||
{
|
||||
return m_value_size;
|
||||
}
|
||||
|
||||
Graph& Graph::value_size(float value_size)
|
||||
{
|
||||
m_value_size = value_size;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from ._scenepic import Graph, Margin
|
||||
from ._scenepic import Graph, Margin, VerticalRule
|
||||
|
||||
Graph.Margin = Margin
|
||||
Graph.VerticalRule = VerticalRule
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
|
@ -28,6 +30,25 @@ class Graph:
|
|||
left: float
|
||||
"""The top margin in pixels."""
|
||||
|
||||
class VerticalRule:
|
||||
"""Represents a vertical line in a sparkline graph."""
|
||||
|
||||
def __init__(self, frame: int, color: np.ndarray, line_width: float):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
frame (int): Frame to add the vertical rule
|
||||
color: (Color, optional): the line color. Defauts to black.
|
||||
line_width: (float, optional): The line width in pixels. Defaults to 1.0f.
|
||||
"""
|
||||
|
||||
frame: int
|
||||
"""The frame at which to add the line."""
|
||||
color: np.ndarray
|
||||
"""The color of the line."""
|
||||
line_width: float
|
||||
"""The width of the line in pixels."""
|
||||
|
||||
@property
|
||||
def canvas_id(self) -> str:
|
||||
"""A unique identifier for the canvas."""
|
||||
|
@ -45,8 +66,25 @@ class Graph:
|
|||
"""The font family used for the graph labels."""
|
||||
|
||||
@property
|
||||
def text_size(self) -> float:
|
||||
"""The size of the graph labels in pixels."""
|
||||
def name_size(self) -> float:
|
||||
"""The size of the sparkline labels in pixels."""
|
||||
|
||||
@property
|
||||
def value_size(self) -> float:
|
||||
"""The size of the sparkline values in pixels."""
|
||||
|
||||
@property
|
||||
def name_align(self) -> str:
|
||||
"""The alignment of the sparkline labels.
|
||||
One of ('left', 'right', 'top', 'bottom', or 'none')
|
||||
"""
|
||||
|
||||
@property
|
||||
def value_align(self) -> str:
|
||||
"""The alignment of the sparkline values.
|
||||
One of ('left', 'right', 'top', 'bottom', or 'none')
|
||||
"""
|
||||
|
||||
|
||||
@property
|
||||
def media_id(self) -> str:
|
||||
|
@ -57,12 +95,14 @@ class Graph:
|
|||
"""
|
||||
|
||||
def add_sparkline(self, name: str, values: np.ndarray,
|
||||
line_color: np.ndarray, line_width: float) -> None:
|
||||
line_color: np.ndarray, line_width: float,
|
||||
vertical_rules: List[VerticalRule] = None) -> None:
|
||||
"""Adds a sparkline to the graph.
|
||||
|
||||
Args:
|
||||
name (str): the name of the measured quantity
|
||||
values (np.ndarray): the measured values
|
||||
line_color (Color, optional): the color of the line (and its labels). Defaults to Black.
|
||||
line_width (float, optional): The width of the line. Defaults to 1.0.
|
||||
line_width (float, optional): The width of the line. Defaults to 1.0.
|
||||
vertical_rules (List[VerticalRule], optional): the vertical rules to add. Defaults to None.
|
||||
"""
|
||||
|
|
|
@ -1834,6 +1834,30 @@ PYBIND11_MODULE(_scenepic, m)
|
|||
.def_readwrite(
|
||||
"left", &Graph::Margin::left, "float: The left margin in pixels");
|
||||
|
||||
py::class_<Graph::VerticalRule>(m, "VerticalRule", R"scenepicdoc(
|
||||
Represents a vertical rule drawn on a sparkline graph.
|
||||
|
||||
Args:
|
||||
frame (int): Frame to add the vertical rule
|
||||
color: (Color, optional): the line color. Defauts to black.
|
||||
line_width: (float, optional): The line width in pixels. Defaults to 1.0f.
|
||||
)scenepicdoc")
|
||||
.def(
|
||||
py::init<std::int64_t, const Color&, float>(),
|
||||
"frame"_a,
|
||||
"color"_a = scenepic::Colors::Black,
|
||||
"line_width"_a = 1.0f)
|
||||
.def_readwrite(
|
||||
"frame",
|
||||
&Graph::VerticalRule::frame,
|
||||
"int: the frame at which to add the line")
|
||||
.def_readwrite(
|
||||
"color", &Graph::VerticalRule::color, "Color: the color of the line")
|
||||
.def_readwrite(
|
||||
"line_width",
|
||||
&Graph::VerticalRule::line_width,
|
||||
"float: the width of the line in pixels");
|
||||
|
||||
py::class_<Graph, std::shared_ptr<Graph>>(
|
||||
m, "Graph", "A 2D viewport that animates one or more sparklines.")
|
||||
.def("__repr__", &Graph::to_string)
|
||||
|
@ -1855,10 +1879,31 @@ PYBIND11_MODULE(_scenepic, m)
|
|||
py::overload_cast<const std::string&>(&Graph::font_family),
|
||||
"str: The font family used for the graph labels")
|
||||
.def_property(
|
||||
"text_size",
|
||||
py::overload_cast<>(&Graph::text_size, py::const_),
|
||||
py::overload_cast<float>(&Graph::text_size),
|
||||
"float: The size of the graph labels in pixels")
|
||||
"name_size",
|
||||
py::overload_cast<>(&Graph::name_size, py::const_),
|
||||
py::overload_cast<float>(&Graph::name_size),
|
||||
"float: The size of the sparkline labels in pixels")
|
||||
.def_property(
|
||||
"value_size",
|
||||
py::overload_cast<>(&Graph::value_size, py::const_),
|
||||
py::overload_cast<float>(&Graph::value_size),
|
||||
"float: The size of the sparkline labels in pixels")
|
||||
.def_property(
|
||||
"name_align",
|
||||
py::overload_cast<>(&Graph::name_align, py::const_),
|
||||
py::overload_cast<const std::string&>(&Graph::name_align),
|
||||
R"scenepicdoc(
|
||||
The alignment of the sparkline labels.
|
||||
One of ('left', 'right', 'top', 'bottom', or 'none')
|
||||
)scenepicdoc")
|
||||
.def_property(
|
||||
"value_align",
|
||||
py::overload_cast<>(&Graph::value_align, py::const_),
|
||||
py::overload_cast<const std::string&>(&Graph::value_align),
|
||||
R"scenepicdoc(
|
||||
The alignment of the sparkline values.
|
||||
One of ('left', 'right', 'top', 'bottom', or 'none')
|
||||
)scenepicdoc")
|
||||
.def_property(
|
||||
"media_id",
|
||||
py::overload_cast<>(&Graph::media_id, py::const_),
|
||||
|
@ -1880,11 +1925,13 @@ PYBIND11_MODULE(_scenepic, m)
|
|||
values (np.ndarray): the measured values
|
||||
line_color (Color, optional): the color of the line (and its labels). Defaults to Black.
|
||||
line_width (float, optional): The width of the line. Defaults to 1.0.
|
||||
vertical_rules (List[VerticalRule], optional): the vertical rules to add. Defaults to None.
|
||||
)scenepicdoc",
|
||||
"name"_a,
|
||||
"values"_a,
|
||||
"line_color"_a = Colors::Black,
|
||||
"line_width"_a = 1.0f);
|
||||
"line_width"_a = 1.0f,
|
||||
"vertical_rules"_a = std::vector<Graph::VerticalRule>{});
|
||||
|
||||
py::class_<DropDownMenu, std::shared_ptr<DropDownMenu>>(
|
||||
m, "DropDownMenu", "Represents a ScenePic DropDownMenu UI component.")
|
||||
|
@ -2075,13 +2122,20 @@ PYBIND11_MODULE(_scenepic, m)
|
|||
automatically populated if not provided). Defaults to None.
|
||||
width (int, optional): the width in pixels of the Canvas on the HTML page. Defaults to 400.
|
||||
height (int, optional): the height in pixels of the Canvas on the HTML page. Defaults to 400.
|
||||
name_align (str, optional): How to align the sparkline label
|
||||
(one of 'left', 'right', 'top', or 'bottom').
|
||||
Defaults to 'left'.
|
||||
value_align (str, optional): How to align the sparkline value
|
||||
(one of 'left', 'right', 'top', or 'bottom').
|
||||
Defaults to 'right'.
|
||||
html_id (str, optional): id of an HTML element to use as this
|
||||
Canvas's parent in the HTML DOM (otherwise simply
|
||||
appended to document). Defaults to None.
|
||||
background_color (np.ndarray, optional): the background color of the canvas. Defaults to White.
|
||||
margin (Margin, optional): the outer margin of the graph. Defaults to Margin(10).
|
||||
font_family (str, optional): the font family used for the graph labels. Defaults to "sans-serif".
|
||||
text_size (float, optional): the text size in pixels used for the graph labels. Defaults to 12.0.
|
||||
name_size (float, optional): the text size in pixels used for the graph labels. Defaults to 12.0.
|
||||
value_size (float, optional): the text size in pixels used for the graph values. Defaults to 12.0.
|
||||
media_id (str, optional): optional ID of a media file to attach to the canvas. This file will be
|
||||
used to drive playback, i.e. frames will be displayed in time with
|
||||
the playback of the media file.
|
||||
|
@ -2092,11 +2146,14 @@ PYBIND11_MODULE(_scenepic, m)
|
|||
"canvas_id"_a = "",
|
||||
"width"_a = 400,
|
||||
"height"_a = 400,
|
||||
"name_align"_a = "left",
|
||||
"value_align"_a = "right",
|
||||
"html_id"_a = "",
|
||||
"background_color"_a = Colors::White,
|
||||
"margin"_a = Graph::Margin(10),
|
||||
"font_family"_a = "sans-serif",
|
||||
"text_size"_a = 12.0,
|
||||
"name_size"_a = 12.0,
|
||||
"value_size"_a = 12.0,
|
||||
"media_id"_a = "")
|
||||
.def(
|
||||
"create_mesh_",
|
||||
|
@ -2454,11 +2511,19 @@ PYBIND11_MODULE(_scenepic, m)
|
|||
title (str, optional): the HTML title. Defaults to "ScenePic".
|
||||
head_html (str, optional): the raw HTML to place in the HEAD tag. Defaults to None.
|
||||
body_html (str, optional): the raw HTML to place in the BODY tag. Defaults to None.
|
||||
script_path (str, optional): desired relative path for the script. A value of None
|
||||
indicates to embed the script in the HTML page.
|
||||
Defaults to None.
|
||||
library_path (str, optional): desired relative path for the library. A value of None
|
||||
indicates to embed the library in the HTML page.
|
||||
Defaults to None.
|
||||
)scenepicdoc",
|
||||
"path"_a,
|
||||
"title"_a = "ScenePic ",
|
||||
"head_html"_a = "",
|
||||
"body_html"_a = "")
|
||||
"body_html"_a = "",
|
||||
"script_path"_a = "",
|
||||
"library_path"_a = "")
|
||||
.def_property_readonly(
|
||||
"script_cleared",
|
||||
&Scene::script_cleared,
|
||||
|
|
|
@ -156,11 +156,14 @@ namespace scenepic
|
|||
const std::string& canvas_id_init,
|
||||
double width,
|
||||
double height,
|
||||
const std::string& name_align,
|
||||
const std::string& value_align,
|
||||
const std::string& html_id,
|
||||
const Color& background_color,
|
||||
const Graph::Margin& margin,
|
||||
const std::string& font_family,
|
||||
float text_size,
|
||||
float name_size,
|
||||
float value_size,
|
||||
const std::string& media_id)
|
||||
{
|
||||
std::string canvas_id = canvas_id_init;
|
||||
|
@ -174,7 +177,10 @@ namespace scenepic
|
|||
canvas->media_id(media_id);
|
||||
canvas->margin(margin);
|
||||
canvas->font_family(font_family);
|
||||
canvas->text_size(text_size);
|
||||
canvas->name_align(name_align);
|
||||
canvas->name_size(name_size);
|
||||
canvas->value_align(value_align);
|
||||
canvas->value_size(value_size);
|
||||
m_graphs.push_back(canvas);
|
||||
m_num_canvases += 1;
|
||||
|
||||
|
@ -747,7 +753,9 @@ namespace scenepic
|
|||
const std::string& path,
|
||||
const std::string& title,
|
||||
const std::string& head_html,
|
||||
const std::string& body_html)
|
||||
const std::string& body_html,
|
||||
const std::string& script_path,
|
||||
const std::string& library_path)
|
||||
{
|
||||
if (m_script_cleared)
|
||||
{
|
||||
|
@ -756,6 +764,16 @@ namespace scenepic
|
|||
"save_as_html().");
|
||||
}
|
||||
|
||||
std::string script = this->script();
|
||||
std::string path_to_script = "";
|
||||
if (!script_path.empty())
|
||||
{
|
||||
std::ofstream script_file(script_path);
|
||||
script_file << script;
|
||||
script = "";
|
||||
path_to_script = " src='" + script_path + "'";
|
||||
}
|
||||
|
||||
std::string lib = "";
|
||||
std::string path_to_lib = "";
|
||||
std::stringstream buff;
|
||||
|
@ -765,6 +783,14 @@ namespace scenepic
|
|||
}
|
||||
lib = buff.str();
|
||||
|
||||
if (!library_path.empty())
|
||||
{
|
||||
std::ofstream lib_file(library_path);
|
||||
lib_file << lib;
|
||||
lib = "";
|
||||
path_to_lib = " src='" + library_path + "'";
|
||||
}
|
||||
|
||||
std::ofstream html(path);
|
||||
html << "<!DOCTYPE html>" << std::endl
|
||||
<< "<html lang=\"en\">" << std::endl
|
||||
|
@ -773,7 +799,8 @@ namespace scenepic
|
|||
<< " <title>" << title << "</title>" << std::endl
|
||||
<< " <script" << path_to_lib << ">" << lib << "</script>"
|
||||
<< std::endl
|
||||
<< " <script>" << this->script() << "</script>" << std::endl
|
||||
<< " <script" << path_to_script << ">" << script << "</script>"
|
||||
<< std::endl
|
||||
<< " " << head_html << std::endl
|
||||
<< " </head>" << std::endl
|
||||
<< " <body>" << std::endl
|
||||
|
|
|
@ -120,8 +120,10 @@ class Scene:
|
|||
"""
|
||||
|
||||
def create_graph(self, canvas_id: str, width: int, height: int,
|
||||
name_align: str, value_align: str,
|
||||
html_id: str, background_color: np.ndarray,
|
||||
margin: Graph.Margin, font_family: str, text_size: float,
|
||||
margin: Graph.Margin, font_family: str,
|
||||
name_size: float, value_size: float,
|
||||
media_id: str) -> Graph:
|
||||
"""Create a new Graph canvas and append to the Scene.
|
||||
|
||||
|
@ -130,13 +132,20 @@ class Scene:
|
|||
automatically populated if not provided). Defaults to None.
|
||||
width (int, optional): the width in pixels of the Canvas on the HTML page. Defaults to 400.
|
||||
height (int, optional): the height in pixels of the Canvas on the HTML page. Defaults to 400.
|
||||
name_align (str, optional): How to align the sparkline label
|
||||
(one of 'left', 'right', 'top', or 'bottom').
|
||||
Defaults to 'left'.
|
||||
value_align (str, optional): How to align the sparkline value
|
||||
(one of 'left', 'right', 'top', or 'bottom').
|
||||
Defaults to 'right'.
|
||||
html_id (str, optional): id of an HTML element to use as this
|
||||
Canvas's parent in the HTML DOM (otherwise simply
|
||||
appended to document). Defaults to None.
|
||||
background_color (np.ndarray, optional): the background color of the canvas. Defaults to White.
|
||||
margin (Margin, optional): the outer margin of the graph. Defaults to Margin(10).
|
||||
font_family (str, optional): the font family used for the graph labels. Defaults to "sans-serif".
|
||||
text_size (float, optional): the text size in pixels used for the graph labels. Defaults to 12.0.
|
||||
name_size (float, optional): the text size in pixels used for the graph labels. Defaults to 12.0.
|
||||
value_size (float, optional): the text size in pixels used for the graph values. Defaults to 12.0.
|
||||
media_id (str, optional): optional ID of a media file to attach to the canvas. This file will be
|
||||
used to drive playback, i.e. frames will be displayed in time with
|
||||
the playback of the media file.
|
||||
|
@ -466,6 +475,12 @@ class Scene:
|
|||
title (str, optional): the HTML title. Defaults to "ScenePic".
|
||||
head_html (str, optional): the raw HTML to place in the HEAD tag. Defaults to None.
|
||||
body_html (str, optional): the raw HTML to place in the BODY tag. Defaults to None.
|
||||
script_path (str, optional): desired relative path for the script. A value of None
|
||||
indicates to embed the script in the HTML page.
|
||||
Defaults to None.
|
||||
library_path (str, optional): desired relative path for the library. A value of None
|
||||
indicates to embed the library in the HTML page.
|
||||
Defaults to None.
|
||||
"""
|
||||
|
||||
@property
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
__version__ = "1.0.12"
|
||||
__version__ = "1.0.14"
|
||||
|
|
|
@ -20,21 +20,38 @@
|
|||
{
|
||||
"CommandType": "SetTextStyle",
|
||||
"FontFamily": "sans-serif",
|
||||
"SizeInPixels": 12.0
|
||||
"NameAlign": "top",
|
||||
"NameSizeInPixels": 32.0,
|
||||
"ValueAlign": "bottom",
|
||||
"ValueSizeInPixels": 10.0
|
||||
},
|
||||
{
|
||||
"CommandType": "AddSparkline",
|
||||
"LineWidth": 1.0,
|
||||
"Name": "cos",
|
||||
"StrokeStyle": "#ff0000",
|
||||
"ValueBuffer": "eAEBeACH/wAAgD8K2X8/NmR/P6Whfj+UkX0/VTR8P1OKej8PlHg/IlJ2PzzFcz8k7nA/t81tP+lkaj/DtGY/Zb5iPwODXj/nA1o/cEJVPxFAUD9O/ko/w35FPxvDPz8VzTk/gp4zP0M5LT9LnyY/nNIfP0jVGD9vqRE/QFEKP+lWLNseAAAAAQ=="
|
||||
"ValueBuffer": "eAEBeACH/wAAgD8K2X8/NmR/P6Whfj+UkX0/VTR8P1OKej8PlHg/IlJ2PzzFcz8k7nA/t81tP+lkaj/DtGY/Zb5iPwODXj/nA1o/cEJVPxFAUD9O/ko/w35FPxvDPz8VzTk/gp4zP0M5LT9LnyY/nNIfP0jVGD9vqRE/QFEKP+lWLNseAAAAAQ==",
|
||||
"VerticalRules":
|
||||
[
|
||||
{
|
||||
"FrameIndex": 10,
|
||||
"LineWidth": 2.0,
|
||||
"StrokeStyle": "#ff0000"
|
||||
},
|
||||
{
|
||||
"FrameIndex": 20,
|
||||
"LineWidth": 1.0,
|
||||
"StrokeStyle": "#00be00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"CommandType": "AddSparkline",
|
||||
"LineWidth": 2.0,
|
||||
"Name": "sin",
|
||||
"StrokeStyle": "#000000",
|
||||
"ValueBuffer": "eAEBeACH/wAAAAChNg09IyGNPQF80z1Byww+pq0vPpNaUj55x3Q+73SLPrBbnD7ZEq0+VJW9PhzezT476N0+z67tPgst/T4cLwY/254NP3/jFD/S+hs/q+IiP/GYKT+XGzA/pGg2Pyx+PD9UWkI/VftHP3hfTT8YhVI/pGpXPwyRL3seAAAAAQ=="
|
||||
"ValueBuffer": "eAEBeACH/wAAAAChNg09IyGNPQF80z1Byww+pq0vPpNaUj55x3Q+73SLPrBbnD7ZEq0+VJW9PhzezT476N0+z67tPgst/T4cLwY/254NP3/jFD/S+hs/q+IiP/GYKT+XGzA/pGg2Pyx+PD9UWkI/VftHP3hfTT8YhVI/pGpXPwyRL3seAAAAAQ==",
|
||||
"VerticalRules": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -29,7 +29,11 @@ int test_graph()
|
|||
|
||||
scenepic::Scene scene;
|
||||
auto graph = scene.create_graph();
|
||||
graph->add_sparkline("cos", cos_t, scenepic::Colors::Red);
|
||||
graph->name_align("top").value_align("bottom").name_size(32).value_size(10);
|
||||
std::vector<scenepic::Graph::VerticalRule> rules;
|
||||
rules.emplace_back(10, scenepic::Colors::Red, 2.0f);
|
||||
rules.emplace_back(20, scenepic::Colors::Green, 1.0f);
|
||||
graph->add_sparkline("cos", cos_t, scenepic::Colors::Red, 1.0, rules);
|
||||
graph->add_sparkline("sin", sin_t, scenepic::Colors::Black, 2.0f);
|
||||
|
||||
test::assert_equal(graph->to_json(), "graph", result);
|
||||
|
|
|
@ -589,8 +589,12 @@ def test_ui_parameters(assert_json_equal):
|
|||
|
||||
def test_graph(assert_json_equal):
|
||||
scene = sp.Scene()
|
||||
graph = scene.create_graph()
|
||||
graph = scene.create_graph(name_align="top", value_align="bottom",
|
||||
name_size=32, value_size=10)
|
||||
t = np.linspace(0, 1, 30)
|
||||
graph.add_sparkline("cos", np.cos(t), sp.Colors.Red)
|
||||
graph.add_sparkline("cos", np.cos(t), sp.Colors.Red,
|
||||
vertical_rules=[
|
||||
sp.Graph.VerticalRule(10, sp.Colors.Red, 2.0),
|
||||
sp.Graph.VerticalRule(20, sp.Colors.Green)])
|
||||
graph.add_sparkline("sin", np.sin(t), line_width=2)
|
||||
assert_json_equal(str(graph), "graph")
|
||||
|
|
432
tssrc/Graph.ts
432
tssrc/Graph.ts
|
@ -1,17 +1,14 @@
|
|||
import * as $ from "jquery"
|
||||
import Misc from "./Misc"
|
||||
import {ObjectCache, CanvasBase} from "./CanvasBase"
|
||||
import { ObjectCache, CanvasBase } from "./CanvasBase"
|
||||
|
||||
class Margin {
|
||||
readonly left: number;
|
||||
readonly top: number;
|
||||
readonly right: number;
|
||||
readonly bottom: number;
|
||||
|
||||
class Rect
|
||||
{
|
||||
top : number;
|
||||
right : number;
|
||||
bottom : number;
|
||||
left : number;
|
||||
|
||||
constructor(top: number, right: number, bottom : number, left : number)
|
||||
{
|
||||
constructor(top: number, right: number, bottom: number, left: number) {
|
||||
this.top = top;
|
||||
this.right = right;
|
||||
this.bottom = bottom;
|
||||
|
@ -19,86 +16,284 @@ class Rect
|
|||
}
|
||||
}
|
||||
|
||||
class Sparkline
|
||||
{
|
||||
|
||||
class Rect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
constructor(x = 0, y = 0, width = 0, height = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
get topEdge() : number {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
get bottomEdge() : number {
|
||||
return this.y + this.height;
|
||||
}
|
||||
|
||||
get leftEdge() : number {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
get rightEdge() : number {
|
||||
return this.x + this.width;
|
||||
}
|
||||
|
||||
Move(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
Resize(width: number, height: number) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
Pad(padding: number): Rect {
|
||||
return new Rect(this.x + padding, this.y + padding,
|
||||
this.width - 2 * padding, this.height - 2 * padding);
|
||||
}
|
||||
|
||||
Outline(context: CanvasRenderingContext2D) {
|
||||
context.save();
|
||||
context.strokeStyle = "#000000";
|
||||
context.strokeRect(this.x, this.y, this.width, this.height);
|
||||
context.restore();
|
||||
}
|
||||
|
||||
DrawText(context: CanvasRenderingContext2D, text: string, fillStyle: string, font: string) {
|
||||
context.save();
|
||||
context.font = font;
|
||||
context.fillStyle = fillStyle;
|
||||
let measurement = context.measureText(text);
|
||||
let textWidth = measurement.width * window.devicePixelRatio;
|
||||
let textHeight = (measurement.actualBoundingBoxAscent + measurement.actualBoundingBoxDescent) * window.devicePixelRatio
|
||||
let x = this.x + 0.5 * (this.width - textWidth);
|
||||
let y = this.y + 0.5 * (this.height + textHeight);
|
||||
context.fillText(text, x, y);
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
|
||||
class Layout {
|
||||
top: Rect | Layout;
|
||||
bottom: Rect | Layout;
|
||||
left: Rect | Layout;
|
||||
right: Rect | Layout;
|
||||
center: Rect | Layout;
|
||||
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
get topEdge() : number {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
get bottomEdge() : number {
|
||||
return this.y + this.height;
|
||||
}
|
||||
|
||||
get leftEdge() : number {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
get rightEdge() : number {
|
||||
return this.x + this.width;
|
||||
}
|
||||
|
||||
constructor({top = new Rect(),
|
||||
right = new Rect(),
|
||||
bottom = new Rect(),
|
||||
left = new Rect(),
|
||||
center = new Rect()} : {top? : Rect | Layout, right? : Rect | Layout, bottom? : Rect | Layout, left? : Rect | Layout, center? : Rect | Layout} = {}) {
|
||||
this.x = this.y = this.width = this.height = 0;
|
||||
this.top = top;
|
||||
this.right = right;
|
||||
this.bottom = bottom;
|
||||
this.left = left;
|
||||
this.center = center;
|
||||
}
|
||||
|
||||
Move(x: number, y: number) {
|
||||
this.top.Move(x + this.top.x - this.x, y + this.top.y - this.y);
|
||||
this.bottom.Move(x + this.bottom.x - this.x, y + this.bottom.y - this.y);
|
||||
this.left.Move(x + this.left.x - this.x, y + this.left.y - this.y);
|
||||
this.right.Move(x + this.right.x - this.x, y + this.right.y - this.y);
|
||||
this.center.Move(x + this.center.x - this.x, y + this.center.y - this.y);
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
Outline(context: CanvasRenderingContext2D) {
|
||||
let panels = [this.top, this.bottom, this.left, this.right, this.center];
|
||||
for (let panel of panels) {
|
||||
panel.Outline(context);
|
||||
}
|
||||
}
|
||||
|
||||
Layout(width: number, height: number) {
|
||||
if (this.top instanceof Layout) {
|
||||
this.top.Layout(0, 0);
|
||||
}
|
||||
|
||||
this.top.Move(0, 0);
|
||||
|
||||
if (this.left instanceof Layout) {
|
||||
this.left.Layout(0, 0);
|
||||
}
|
||||
|
||||
this.left.Move(0, this.top.bottomEdge);
|
||||
|
||||
if (this.right instanceof Layout) {
|
||||
this.right.Layout(0, 0);
|
||||
}
|
||||
|
||||
this.right.Move(this.left.rightEdge, this.top.bottomEdge);
|
||||
|
||||
if (this.bottom instanceof Layout) {
|
||||
this.bottom.Layout(0, 0);
|
||||
}
|
||||
|
||||
this.bottom.Move(0, this.height);
|
||||
|
||||
let centerWidth = Math.max(0, width - this.left.width - this.right.width);
|
||||
let centerHeight = Math.max(Math.max(this.left.height, this.right.height), height - this.top.height - this.bottom.height);
|
||||
|
||||
if (this.center instanceof Layout) {
|
||||
this.center.Layout(centerWidth, centerHeight);
|
||||
} else {
|
||||
this.center.Resize(centerWidth, centerHeight);
|
||||
}
|
||||
|
||||
this.width = this.left.width + this.center.width + this.right.width;
|
||||
this.height = this.top.height + this.center.height + this.bottom.height;
|
||||
this.center.Move(this.left.rightEdge, this.top.bottomEdge);
|
||||
this.top.Move(0.5 * (this.width - this.top.width), 0);
|
||||
this.left.Move(0, 0.5 * (this.height - this.left.height));
|
||||
this.right.Move(this.center.rightEdge, 0.5 * (this.height - this.right.height));
|
||||
this.bottom.Move(0.5 * (this.width - this.bottom.width), this.center.bottomEdge);
|
||||
}
|
||||
}
|
||||
|
||||
class VerticalRule {
|
||||
frame: number;
|
||||
strokeStyle: string;
|
||||
lineWidth: number;
|
||||
|
||||
constructor(frame: number, strokeStyle: string, lineWidth: number) {
|
||||
this.frame = frame;
|
||||
this.strokeStyle = strokeStyle;
|
||||
this.lineWidth = lineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
class Sparkline {
|
||||
static readonly MARKER_RADIUS = 4;
|
||||
|
||||
values : Float32Array;
|
||||
name : string;
|
||||
strokeStyle : string;
|
||||
lineWidth : number;
|
||||
minValue : number;
|
||||
maxValue : number;
|
||||
values: Float32Array;
|
||||
name: string;
|
||||
strokeStyle: string;
|
||||
lineWidth: number;
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
verticalRules: VerticalRule[];
|
||||
|
||||
constructor(name : string, values : Float32Array, strokeStyle : string, lineWidth : number)
|
||||
{
|
||||
constructor(name: string, values: Float32Array, strokeStyle: string, lineWidth: number, verticalRules: VerticalRule[]) {
|
||||
this.name = name;
|
||||
this.values = values;
|
||||
this.strokeStyle = strokeStyle;
|
||||
this.lineWidth = lineWidth;
|
||||
this.minValue = Math.min(...values);
|
||||
this.maxValue = Math.max(...values);
|
||||
this.verticalRules = verticalRules;
|
||||
}
|
||||
|
||||
Draw(context : CanvasRenderingContext2D, box : Rect, frameIndex : number)
|
||||
{
|
||||
context.beginPath();
|
||||
let xScale = (box.right - box.left) / this.values.length;
|
||||
Draw(context: CanvasRenderingContext2D, box: Rect, frameIndex: number) {
|
||||
let xScale = box.width / this.values.length;
|
||||
let yScale = 0;
|
||||
let yOffset = 0.5 * (box.top + box.bottom);
|
||||
if(this.minValue != this.maxValue){
|
||||
yOffset = box.bottom;
|
||||
yScale = (box.bottom - box.top)/(this.maxValue - this.minValue);
|
||||
let yOffset = box.y + 0.5 * box.height;
|
||||
if (this.minValue != this.maxValue) {
|
||||
yOffset = box.y + box.height;
|
||||
yScale = box.height / (this.maxValue - this.minValue);
|
||||
}
|
||||
|
||||
let x = box.left;
|
||||
for (let rule of this.verticalRules) {
|
||||
let x = box.x + rule.frame * xScale;
|
||||
context.beginPath();
|
||||
context.moveTo(x, box.y);
|
||||
context.lineTo(x, box.y + box.height);
|
||||
|
||||
context.save()
|
||||
if (rule.strokeStyle != null && rule.lineWidth > 0) {
|
||||
context.strokeStyle = rule.strokeStyle;
|
||||
context.lineWidth = rule.lineWidth;
|
||||
}
|
||||
context.stroke();
|
||||
context.restore();
|
||||
}
|
||||
|
||||
let x = box.x;
|
||||
let y = yOffset - (this.values[0] - this.minValue) * yScale;
|
||||
context.beginPath();
|
||||
context.moveTo(x, y);
|
||||
for(let value of this.values.slice(1))
|
||||
{
|
||||
for (let value of this.values.slice(1)) {
|
||||
x += xScale;
|
||||
y = yOffset - (value - this.minValue) * yScale;
|
||||
context.lineTo(x, y);
|
||||
}
|
||||
|
||||
if (this.strokeStyle != null && this.lineWidth > 0)
|
||||
{
|
||||
context.save()
|
||||
if (this.strokeStyle != null && this.lineWidth > 0) {
|
||||
context.strokeStyle = this.strokeStyle;
|
||||
context.lineWidth = this.lineWidth;
|
||||
context.stroke();
|
||||
}
|
||||
context.stroke();
|
||||
context.restore();
|
||||
|
||||
x = box.left + xScale * frameIndex;
|
||||
x = box.x + xScale * frameIndex;
|
||||
y = yOffset - (this.values[frameIndex] - this.minValue) * yScale;
|
||||
context.beginPath();
|
||||
context.arc(x, y, Sparkline.MARKER_RADIUS * window.devicePixelRatio, 0.0, 2.0 * Math.PI, false);
|
||||
context.fillStyle = this.strokeStyle;
|
||||
context.save();
|
||||
if (this.strokeStyle != null) {
|
||||
context.fillStyle = this.strokeStyle;
|
||||
}
|
||||
context.fill();
|
||||
context.restore();
|
||||
}
|
||||
|
||||
ValueAt(frameIndex : number) : number
|
||||
{
|
||||
ValueAt(frameIndex: number): number {
|
||||
return this.values[frameIndex];
|
||||
}
|
||||
}
|
||||
|
||||
export default class Graph extends CanvasBase
|
||||
{
|
||||
context : CanvasRenderingContext2D = null; // Rendering context
|
||||
export default class Graph extends CanvasBase {
|
||||
context: CanvasRenderingContext2D = null; // Rendering context
|
||||
|
||||
backgroundStyle : string; // Background color
|
||||
backgroundStyle: string; // Background color
|
||||
|
||||
fontFamily : string;
|
||||
sizeInPixels : number;
|
||||
fontFamily: string;
|
||||
nameSizeInPixels: number;
|
||||
nameAlign: string;
|
||||
valueSizeInPixels: number;
|
||||
valueAlign: string;
|
||||
|
||||
sparklines : Sparkline[] = []; // The sparklines
|
||||
sparklines: Sparkline[] = []; // The sparklines
|
||||
|
||||
margin : Rect = null;
|
||||
margin: Margin = null;
|
||||
|
||||
static readonly PADDING = 7;
|
||||
|
||||
constructor(canvasId : string, public frameRate : number, public width : number, public height : number, objectCache : ObjectCache, public SetStatus : (status : string) => void, public SetWarning : (message : string) => void, public RequestRedraw : () => void, public ReportFrameIdChange : (canvasId : string, frameId : string) => void)
|
||||
{
|
||||
constructor(canvasId: string, public frameRate: number, public width: number, public height: number, objectCache: ObjectCache, public SetStatus: (status: string) => void, public SetWarning: (message: string) => void, public RequestRedraw: () => void, public ReportFrameIdChange: (canvasId: string, frameId: string) => void) {
|
||||
// Base class constructor
|
||||
super(canvasId, frameRate, width, height, objectCache, SetStatus, SetWarning, RequestRedraw, ReportFrameIdChange);
|
||||
|
||||
|
@ -106,24 +301,27 @@ export default class Graph extends CanvasBase
|
|||
this.context = this.htmlCanvas.getContext('2d');
|
||||
|
||||
this.backgroundStyle = "#000000";
|
||||
this.margin = new Rect(10, 10, 10, 10);
|
||||
this.margin = new Margin(10, 10, 10, 10);
|
||||
this.nameAlign = "left";
|
||||
this.valueAlign = "right";
|
||||
|
||||
// Start render loop
|
||||
this.StartRenderLoop();
|
||||
}
|
||||
|
||||
// Execute a single canvas command
|
||||
ExecuteCanvasCommand(command : any)
|
||||
{
|
||||
switch(command["CommandType"])
|
||||
{
|
||||
ExecuteCanvasCommand(command: any) {
|
||||
switch (command["CommandType"]) {
|
||||
case "SetBackgroundStyle":
|
||||
this.backgroundStyle = String(command["Value"]);
|
||||
break;
|
||||
|
||||
case "SetTextStyle":
|
||||
this.fontFamily = String(command["FontFamily"])
|
||||
this.sizeInPixels = Number(command["SizeInPixels"])
|
||||
this.nameSizeInPixels = Number(command["NameSizeInPixels"])
|
||||
this.nameAlign = String(command["NameAlign"])
|
||||
this.valueSizeInPixels = Number(command["ValueSizeInPixels"])
|
||||
this.valueAlign = String(command["ValueAlign"])
|
||||
break;
|
||||
|
||||
case "SetMargin":
|
||||
|
@ -132,22 +330,26 @@ export default class Graph extends CanvasBase
|
|||
let right = Number(margin["Right"]);
|
||||
let bottom = Number(margin["Bottom"]);
|
||||
let left = Number(margin["Left"]);
|
||||
this.margin = new Rect(top, right, bottom, left);
|
||||
this.margin = new Margin(top, right, bottom, left);
|
||||
break;
|
||||
|
||||
case "AddSparkline":
|
||||
case "AddSparkline":
|
||||
let name = String(command["Name"]);
|
||||
let values = Misc.Base64ToFloat32Array(command["ValueBuffer"]);
|
||||
let strokeStyle = String(command["StrokeStyle"]);
|
||||
let lineWidth = Number(command["LineWidth"]);
|
||||
if(this.sparklines.length == 0){
|
||||
for(let i=0; i<values.length; ++i)
|
||||
{
|
||||
let verticalRules: VerticalRule[] = []
|
||||
for (let rule of command["VerticalRules"]) {
|
||||
verticalRules.push(new VerticalRule(Number(rule["FrameIndex"]), String(rule["StrokeStyle"]), Number(rule["LineWidth"])));
|
||||
}
|
||||
|
||||
if (this.sparklines.length == 0) {
|
||||
for (let i = 0; i < values.length; ++i) {
|
||||
this.AddFrame(i.toString());
|
||||
}
|
||||
}
|
||||
|
||||
this.sparklines.push(new Sparkline(name, values, strokeStyle, lineWidth));
|
||||
this.sparklines.push(new Sparkline(name, values, strokeStyle, lineWidth, verticalRules));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -156,17 +358,14 @@ export default class Graph extends CanvasBase
|
|||
}
|
||||
}
|
||||
|
||||
AllocateFrame()
|
||||
{
|
||||
AllocateFrame() {
|
||||
}
|
||||
|
||||
DeallocateFrame(frameIndex : number)
|
||||
{
|
||||
DeallocateFrame(frameIndex: number) {
|
||||
}
|
||||
|
||||
// Render scene method
|
||||
Render()
|
||||
{
|
||||
Render() {
|
||||
// Clear
|
||||
this.context.fillStyle = this.backgroundStyle;
|
||||
this.context.fillRect(0, 0, this.htmlCanvas.clientWidth * window.devicePixelRatio, this.htmlCanvas.clientHeight * window.devicePixelRatio);
|
||||
|
@ -177,41 +376,98 @@ export default class Graph extends CanvasBase
|
|||
let top = this.margin.top * window.devicePixelRatio;
|
||||
width *= window.devicePixelRatio;
|
||||
height *= window.devicePixelRatio;
|
||||
let padding = Graph.PADDING * window.devicePixelRatio;
|
||||
|
||||
this.context.font = this.sizeInPixels + "px " + this.fontFamily;
|
||||
let layout = new Layout();
|
||||
layout.center = new Rect();
|
||||
|
||||
let nameFont = this.nameSizeInPixels + "px " + this.fontFamily;
|
||||
let valueFont = this.valueSizeInPixels + "px " + this.fontFamily;
|
||||
let nameColumnWidth = 0;
|
||||
let valueColumnWidth = 0;
|
||||
for(let sparkline of this.sparklines){
|
||||
for (let sparkline of this.sparklines) {
|
||||
this.context.font = nameFont;
|
||||
let nameSize = this.context.measureText(sparkline.name).width;
|
||||
this.context.font = valueFont;
|
||||
let minValSize = this.context.measureText(sparkline.minValue.toFixed(2)).width;
|
||||
let maxValSize = this.context.measureText(sparkline.maxValue.toFixed(2)).width;
|
||||
nameColumnWidth = Math.max(nameSize, nameColumnWidth);
|
||||
valueColumnWidth = Math.max(minValSize, maxValSize, valueColumnWidth);
|
||||
}
|
||||
|
||||
nameColumnWidth *= window.devicePixelRatio;
|
||||
let nameHeight = this.nameSizeInPixels * window.devicePixelRatio;
|
||||
valueColumnWidth *= window.devicePixelRatio;
|
||||
let valueHeight = this.valueSizeInPixels * window.devicePixelRatio;
|
||||
|
||||
let rowHeight = height / this.sparklines.length;
|
||||
let boxWidth = width - nameColumnWidth - valueColumnWidth - 2 * padding;
|
||||
let boxHeight = rowHeight - 2 * padding;
|
||||
let y = top;
|
||||
let boxLeft = left + nameColumnWidth + padding;
|
||||
let boxRight = boxLeft + boxWidth - padding;
|
||||
for(let sparkline of this.sparklines){
|
||||
let box = new Rect(y + padding, boxRight, y + boxHeight, boxLeft);
|
||||
sparkline.Draw(this.context, box, this.currentFrameIndex);
|
||||
let rowTop = top;
|
||||
for (let sparkline of this.sparklines) {
|
||||
let layout = new Layout();
|
||||
|
||||
this.context.fillStyle = sparkline.strokeStyle;
|
||||
let textSize = this.context.measureText(sparkline.name);
|
||||
let text_top = y + window.devicePixelRatio * 0.5 * rowHeight;
|
||||
let text_left = left + window.devicePixelRatio * 0.5 * (nameColumnWidth - textSize.width);
|
||||
this.context.fillText(sparkline.name, text_left, text_top);
|
||||
|
||||
let value = sparkline.ValueAt(this.currentFrameIndex).toFixed(2);
|
||||
textSize = this.context.measureText(value);
|
||||
text_left = boxRight + window.devicePixelRatio * 0.5 * (valueColumnWidth - textSize.width)
|
||||
this.context.fillText(value, text_left, text_top);
|
||||
let nameRect = new Rect(0, 0, nameColumnWidth, nameHeight);
|
||||
let nameLayout = new Layout({left: nameRect});
|
||||
switch (this.nameAlign) {
|
||||
case "top":
|
||||
layout.top = nameLayout;
|
||||
break;
|
||||
|
||||
y += rowHeight;
|
||||
case "bottom":
|
||||
layout.bottom = nameLayout;
|
||||
break;
|
||||
|
||||
case "left":
|
||||
layout.left = nameLayout;
|
||||
break;
|
||||
|
||||
case "right":
|
||||
layout.right = nameLayout;
|
||||
break;
|
||||
}
|
||||
|
||||
let valueRect = new Rect(0, 0, valueColumnWidth, valueHeight);
|
||||
switch (this.valueAlign) {
|
||||
case "top":
|
||||
if(layout.top instanceof Layout){
|
||||
layout.top.right = valueRect;
|
||||
}else{
|
||||
layout.top = new Layout({right: valueRect})
|
||||
}
|
||||
break;
|
||||
|
||||
case "bottom":
|
||||
if(layout.bottom instanceof Layout){
|
||||
layout.bottom.right = valueRect;
|
||||
}else{
|
||||
layout.bottom = new Layout({right: valueRect})
|
||||
}
|
||||
break;
|
||||
|
||||
case "left":
|
||||
if(layout.left instanceof Layout){
|
||||
layout.left.right = valueRect;
|
||||
}else{
|
||||
layout.left = new Layout({right: valueRect})
|
||||
}
|
||||
break;
|
||||
|
||||
case "right":
|
||||
if(layout.right instanceof Layout){
|
||||
layout.right.right = valueRect;
|
||||
}else{
|
||||
layout.right = new Layout({right: valueRect})
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
layout.Layout(width, rowHeight);
|
||||
layout.Move(left, rowTop);
|
||||
|
||||
nameRect.DrawText(this.context, sparkline.name, sparkline.strokeStyle, nameFont);
|
||||
valueRect.DrawText(this.context, sparkline.ValueAt(this.currentFrameIndex).toFixed(2),
|
||||
sparkline.strokeStyle, valueFont);
|
||||
sparkline.Draw(this.context, (layout.center as Rect).Pad(Graph.PADDING * window.devicePixelRatio),
|
||||
this.currentFrameIndex);
|
||||
rowTop += rowHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче