def _generate_shapes(self) -> Dict[str, Shape]: arrow1 = Arrow( self._edge + (self._edge - self._center) * self._offset, self._edge ) text = LineAnnotation(self._text, arrow1, TextPosition.START) text.style.font_size = 24 ret_dict = {"arrow1": arrow1, "text": text} if self._diameter: inward_vector = self._center - self._edge offset_vector = inward_vector.unit_vector * self._offset start = self._edge + inward_vector * 2 + offset_vector end = self._edge + inward_vector * 2 ret_dict["arrow2"] = Arrow(start, end) if self._center_mark: ret_dict["center_mark_h"] = Line( self._center - Point(-self._offset * 0.5, 0), self._center - Point(self._offset * 0.5, 0), ) ret_dict["center_mark_v"] = Line( self._center - Point(0, -self._offset * 0.5), self._center - Point(0, self._offset * 0.5), ) if self._center_line: ret_dict["center_line"] = Line(self._edge, self._center) if self._center_line and self._diameter: ret_dict["center_line2"] = Line( self._center, self._center + (self._center - self._edge) ) return ret_dict
def interval(self, x_range: Tuple[float, float] = None, y_range: Tuple[float, float] = None): """Returns a smaller portion of a line. Args: x_range: The range of x-coordinates which should be used to obtain the segment y_range: The range of y-coordinates which should be used to obtain the segment Returns: A line bounded to either ``x_range`` or ``y_range``. Raises: ValueError: If the line is vertical and ``x_range`` is provided, or if the line is horizontal and ``y_range`` is provided. """ if x_range and y_range: raise ValueError("Cannot specify both x_range and y_range.") if x_range is not None: return Line(Point(x_range[0], self(x_range[0])), Point(x_range[1], self(x_range[1]))) elif y_range is not None: return Line(Point(self(y_range[0]), y_range[0]), Point(self(y_range[1]), y_range[1]))
def __init__(self, lower_left_corner: Point, width: float, height: float): self._width = width self._height = height self._lower_left_corner = lower_left_corner super().__init__(self._generate_points()) # Dimensions dims = { "width": DistanceWithText( "width", self._lower_left_corner + Point(0, -height / 5.0), self._lower_left_corner + Point(width, -height / 5.0), ), "height": DistanceWithText( "height", self._lower_left_corner + Point(width + width / 5.0, 0), self._lower_left_corner + Point(width + width / 5.0, height), ), "lower_left_corner": ArrowWithText( "lower_left_corner", self._lower_left_corner - Point(width / 5.0, height / 5.0), self._lower_left_corner, ), } dims["height"]["text"].style.alignment = TextStyle.Alignment.LEFT self.dimensions = dims
def __init__( self, center: Point, radius: float, inner_radius: float = None, nlines: int = 10 ): self._center = center self._radius = radius self._inner_radius = inner_radius self._nlines = nlines if inner_radius is None: self._inner_radius = radius / 5.0 outer = Circle(center, radius) inner = Circle(center, inner_radius) lines = [] # Draw nlines+1 since the first and last coincide # (then nlines lines will be visible) t = np.linspace(0, 2 * np.pi, nlines + 1) xinner = self._center.x + self._inner_radius * np.cos(t) yinner = self._center.y + self._inner_radius * np.sin(t) xouter = self._center.x + self._radius * np.cos(t) youter = self._center.y + self._radius * np.sin(t) lines = [ Line(Point(xi, yi), Point(xo, yo)) for xi, yi, xo, yo in zip(xinner, yinner, xouter, youter) ] super().__init__( { "inner": inner, "outer": outer, "spokes": Composition( {"spoke%d" % i: lines[i] for i in range(len(lines))} ), } )
def _generate_points(self) -> List[Point]: return [ self._lower_left_corner, self._lower_left_corner + Point(self._width, 0), self._lower_left_corner + Point(self._width, self._height), self._lower_left_corner + Point(0, self._height), self._lower_left_corner, ]
def _horizontal_arrow(self) -> DoubleArrow: if self._start.x < self._end.x: return DoubleArrow( Point(self._start.x, self._start.y + self._offset), Point(self._end.x, self._start.y + self._offset), ) else: return DoubleArrow( Point(self._start.x, self._end.y + self._offset), Point(self._end.x, self._end.y + self._offset), )
def _vertical_arrow(self) -> DoubleArrow: if self._start.y > self._end.y: return DoubleArrow( Point(self._end.x + self._offset, self._start.y), Point(self._end.x + self._offset, self._end.y), ) else: return DoubleArrow( Point(self._start.x + self._offset, self._start.y), Point(self._start.x + self._offset, self._end.y), )
def __init__(self, position: Point, size: float): self._position = position self._size = size self._p0 = Point(position.x - size / 2.0, position.y - size) self._p1 = Point(position.x + size / 2.0, position.y - size) self._triangle = Triangle(self._p0, self._p1, position) gap = size / 5.0 self._height = size / 4.0 # height of rectangle self._p2 = Point(self._p0.x, self._p0.y - gap - self._height) self._rectangle = Rectangle(self._p2, self._size, self._height) shapes = {"triangle": self._triangle, "rectangle": self._rectangle} super().__init__(shapes)
def __init__(self, lower_left_corner: Point, width: float, height: float, num_arrows=10): box = Rectangle(lower_left_corner, width, height) shapes = {"box": box} dx = float(width) / (num_arrows - 1) for i in range(num_arrows): x = lower_left_corner.x + i * dx start = Point(x, lower_left_corner.y + height) end = Point(x, lower_left_corner.y) shapes["arrow%d" % i] = Arrow(start, end) super().__init__(shapes)
class LineAnnotation(Text): """Annotates a line with the provided text.""" # TODO: Write a LineAnnotation Example _DEFAULT_SPACING: Point = Point(0.15, 0.15) def __init__(self, text: str, line: Line, text_position: TextPosition = TextPosition.MIDDLE): spacing = self._DEFAULT_SPACING if text_position == TextPosition.START: position = line.start + spacing alignment = TextStyle.Alignment.LEFT elif text_position == TextPosition.END: position = line.end + spacing alignment = TextStyle.Alignment.RIGHT elif text_position == TextPosition.MIDDLE: position = line.start + (line.end - line.start) * 0.5 + spacing alignment = TextStyle.Alignment.CENTER else: raise RuntimeError( f"Invalid value of text_position: {text_position}.") super().__init__(text, position) self.style.alignment = alignment
def __init__( self, start, length, ): Force.__init__(self, "$g$", start, start + Point(0, -length)) self.style.line_color = Style.Color.BLACK
class ArrowWithText(ShapeWithText): """An ``Arrow`` with a text label at ``text_position``. Args: text: The text to be displayed. start: The start ``Point`` of the arrow. end: The end ``Point`` of the arrow. text_position: The position of the text on the arrow. spacing: The text spacing. Examples: >>> arrow_with_text = ps.ArrowWithText( ... "$a$", ... ps.Point(1.0, 1.0), ... ps.Point( ... 3.0, ... 1.0, ... ), ... ) >>> fig = ps.Figure(0.0, 4.0, 0.0, 2.0, backend=MatplotlibBackend) >>> fig.add(arrow_with_text) >>> fig.save("pysketcher/images/arrow_with_text.png") .. figure:: images/arrow_with_text.png :alt: An example of ArrowWithText. :figclass: align-center An example of ``ArrowWithText``. """ _DEFAULT_SPACING: Point = Point(0.15, 0.15) @unique class TextPosition(Enum): """Specifies the position of the text on the ``Arrow``.""" START = auto() END = auto() def __init__( self, text: str, start: Point, end: Point, text_position: TextPosition = TextPosition.START, spacing: Union[float, Point] = None, ): spacing = spacing if spacing else self._DEFAULT_SPACING if not issubclass(spacing.__class__, Point): spacing = Point(spacing, spacing) if text_position == self.TextPosition.START: text = Text(text, start + spacing) if text_position == self.TextPosition.END: text = Text(text, end + spacing) arrow = Arrow(start, end) super().__init__(arrow, text)
def __init__( self, text: str, position: Point, direction: Point = Point(1, 0) # noqa: B008 ): super().__init__() self._text: str = text self._position: Point = position self._direction: Point = direction self._style: TextStyle = TextStyle()
def __init__( self, start: Point, length: float, label: str, rotation_angle: Angle = Angle(0.0), # noqa: B008 label_spacing=1.0 / 4.5, ): arrow = Arrow(start, start + Point(length, 0)).rotate(rotation_angle, start) # should increase spacing for downward pointing axis if type(label_spacing) != tuple: label_spacing = (label_spacing, label_spacing) label_pos = Point( start.x + length + label_spacing[0], start.y + label_spacing[1] ) label = Text(label, label_pos).rotate(rotation_angle, start) super().__init__(arrow, label)
def __init__(self, points: List[Point], degree: int = 3, resolution: int = 501): self._input_points = points self._smooth = UnivariateSpline( [p.x for p in points], [p.y for p in points], s=0, k=degree ) x_coordinates = np.linspace(points[0].x, points[-1].x, resolution) y_coordinates = self._smooth(x_coordinates) smooth_points = [Point(p[0], p[1]) for p in zip(x_coordinates, y_coordinates)] super().__init__(smooth_points)
def __init__(self, position: Point, size: float): p0 = Point(position.x - size / 2.0, position.y - size) p1 = Point(position.x + size / 2.0, position.y - size) triangle = Triangle(p0, p1, position) gap = size / 5.0 h = size / 4.0 # height of rectangle p2 = Point(p0.x, p0.y - gap - h) rectangle = Rectangle(p2, size, h) shapes = {"triangle": triangle, "rectangle": rectangle} super().__init__(shapes) self._dimensions = { "position": Text("position", position), "size": DistanceWithText("size", Point(p2.x, p2.y - size), Point(p2.x + size, p2.y - size)), }
def __init__(self, x_min=0, x_max=6, y_min=0.5, y_max=3.8): xs = np.array([0.0, 2.0, 3.0, 4.0, 5.0, 6.0]) ys = np.array([0.5, 3.5, 3.8, 2, 2.5, 3.5]) # Scale x and y xs = _scale_array(x_min, x_max, xs) ys = _scale_array(y_min, y_max, ys) points = Point.from_coordinate_lists(xs, ys) super().__init__(points) self.style.line_color = Style.Color.BLACK
def __init__( self, start: Point, length: float, label: str, rotation_angle: Angle = Angle(0.0), # noqa: B008 ): arrow = Arrow(start, start + Point(length, 0)).rotate(rotation_angle, start) # should increase spacing for downward pointing axis label = LineAnnotation(label, arrow) super().__init__({"arrow": arrow, "label": label})
def __init__(self, name_pos="start", x_min=0, x_max=6, y_min=0.5, y_max=1.8): xs = np.array([0, 2, 3, 4, 5, 6]) ys = np.array([1.5, 1.3, 0.7, 0.5, 0.6, 0.8]) # Scale x and y xs = _scale_array(x_min, x_max, xs) ys = _scale_array(y_min, y_max, ys) points = Point.from_coordinate_lists(xs, ys) super().__init__(points)
def __init__( self, start: Point, height: float, profile: Callable[[float], Point], num_arrows: int, scaling: float = 1, ): self._start = start self._height = height self._profile = profile self._num_arrows = num_arrows self._scaling = scaling shapes = dict() # Draw left line shapes["start line"] = Line(self._start, (self._start + Point(0, self._height))) # Draw velocity arrows dy = float(self._height) / (self._num_arrows - 1) end_points = [] for i in range(self._num_arrows): start_position = Point(start.x, start.y + i * dy) end_position = start_position + profile( start_position.y) * self._scaling end_points += [end_position] if start_position == end_position: continue shapes["arrow%d" % i] = Arrow(start_position, end_position) shapes["smooth curve"] = Spline(end_points) super().__init__(shapes)
def __init__(self, name=None, name_pos="start", x_min=0, x_max=6, y_min=0, y_max=2): xs = np.array([0.0, 2.0, 3.0, 4.0, 5.0, 6.0]) ys = np.array([1, 1.8, 1.2, 0.7, 0.8, 0.85]) xs = _scale_array(x_min, x_max, xs) ys = _scale_array(y_min, y_max, ys) points = Point.from_coordinate_lists(xs, ys) super().__init__(points)
def __init__(self, x_min=0, x_max=2.25, y_min=0.046679703125, y_max=1.259375): a = 0 b = 2.25 resolution = 100 xs = np.linspace(a, b, resolution + 1) ys = self.__call__(xs) # Scale x and y xs = _scale_array(x_min, x_max, xs) ys = _scale_array(y_min, y_max, ys) points = Point.from_coordinate_lists(xs, ys) super().__init__(points)
def __init__( self, text: str, start: Point, end: Point, text_position: TextPosition = TextPosition.START, spacing: Union[float, Point] = None, ): spacing = spacing if spacing else self._DEFAULT_SPACING if not issubclass(spacing.__class__, Point): spacing = Point(spacing, spacing) if text_position == self.TextPosition.START: text = Text(text, start + spacing) if text_position == self.TextPosition.END: text = Text(text, end + spacing) arrow = Arrow(start, end) super().__init__(arrow, text)
def __call__(self, theta: Angle) -> Point: """Provides a point on the arc ``theta`` of the way around. Args: theta: The angle from the ``start_angle`` from which the point should be taken. Returns: the point ``theta`` of the way around the arc. Raises: ValueError: if ``theta`` is beyond the bounds of the arc. """ if self._arc_angle != 0.0 and theta > self._arc_angle: raise ValueError("Theta is outside the bounds of the arc") iota = Angle(self.start_angle + theta) ret_point = Point( self.center.x + self.radius * np.cos(iota), self.center.y + self.radius * np.sin(iota), ) return ret_point
def scale(self, factor: float) -> "Curve": """Scale all coordinates by `factor`: ``x = factor*x``, etc.""" ret_curve = Curve( Point.from_coordinate_lists(factor * self.xs, factor * self.ys)) ret_curve.style = self.style return ret_curve
def upper_right(self) -> Point: """The upper right point of the rectangle.""" return self._lower_left_corner + Point(self._width, self._height)
def mid_top(self) -> Point: """The middle of the top of the load.""" return self._lower_left_corner + Point(self._width / 2, self._height)
def __init__( self, start: Point, total_length: float, bar_length: float = None, width: float = None, dashpot_length: float = None, piston_pos: float = None, ): B = start L = total_length if width is None: w = L / 10.0 # total width 1/5 of length else: w = width / 2.0 s = bar_length shapes = {} # dashpot is p0-p1 in y and width 2*w if dashpot_length is None: if s is None: f = Dashpot._dashpot_fraction s = L * (1 - f) / 2.0 # default p1 = Point(B.x, B.y + L - s) dashpot_length = f * L else: if s is None: f = 1.0 / 2 # the bar lengths are taken as f*dashpot_length s = f * dashpot_length # default p1 = Point(B.x, B.y + s + dashpot_length) p0 = Point(B.x, B.y + s) p2 = Point(B.x, B.y + L) if not (p2.y > p1.y > p0.y): raise ValueError( ("Dashpot has inconsistent dimensions! start: %g, " "dashpot begin: %g, dashpot end: %g, very end: %g" % (B.y, p0.y, p1.y, p2.y))) shapes["line start"] = Line(B, p0) shapes["pot"] = Curve([ Point(p1.x - w, p1.y), Point(p0.x - w, p0.y), Point(p0.x + w, p0.y), Point(p1.x + w, p1.y), ]) piston_thickness = dashpot_length * Dashpot._piston_thickness_fraction if piston_pos is None: piston_pos = 1 / 3.0 * dashpot_length if piston_pos < 0: piston_pos = 0 elif piston_pos > dashpot_length: piston_pos = dashpot_length - piston_thickness abs_piston_pos = p0.y + piston_pos gap = w * Dashpot._piston_gap_fraction shapes["piston"] = Composition({ "line": Line(p2, Point(B.x, abs_piston_pos + piston_thickness)), "rectangle": Rectangle( Point(B.x - w + gap, abs_piston_pos), 2 * w - 2 * gap, piston_thickness, ), }) shapes["piston"]["rectangle"].set_fill_pattern(Style.FillPattern.CROSS) super().__init__(shapes)
def _segment_function(t: float) -> Point: x = _bernstein_cubic([p0.x, p1.x, p2.x, p3.x], t) y = _bernstein_cubic([p0.y, p1.y, p2.y, p3.y], t) return Point(x, y)