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:
Matthew A Johnson 2022-09-07 17:58:44 +01:00 коммит произвёл GitHub
Родитель 8c39973578
Коммит 575ed31f3e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
22 изменённых файлов: 765 добавлений и 208 удалений

Просмотреть файл

@ -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)

Просмотреть файл

@ -1 +1 @@
1.0.13
1.0.14

Просмотреть файл

@ -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 ---------------------------------------------------

4
package-lock.json сгенерированный
Просмотреть файл

@ -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")

Просмотреть файл

@ -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;
}
}
}