tooltips on Svelte chart made with Layer Cake? - tooltip

I'm trying to add tooltips to a chart I made with Layer Cake, the graphics framework for Svelte. I looked at the Map example on the Layer Cake site, as that one has tooltips, but I can't figure out how to adapt for my bar chart.
I can't even get a string to show up, much less any data. Any help would be greatly appreciated. I think I must be missing something pretty obvious.
Below is a minimal example with dummy data.
You can see the code working in this REPL:
https://svelte.dev/repl/e8bb579754e6405ea19363b5d13d7f54?version=3.55.1
Thanks!
App.svelte:
<script>
import { LayerCake, Svg, Html } from "layercake";
import Bar from "./Bar.svelte";
import AxisX from "./AxisX.svelte";
import AxisY from "./AxisY.svelte";
import Tooltip from "./Tooltip.html.svelte";
import { scaleBand } from "d3-scale";
let data = [
{
fruit: "Apple",
number: 364,
},
{
fruit: "Banana",
number: 263,
},
{
fruit: "Mango",
number: 872,
},
{
fruit: "Pear",
number: 156,
},
]
data.forEach((d) => {
d[xKey] = +d[xKey];
});
const xKey = "number";
const yKey = "fruit";
let evt;
let hideTooltip = false;
</script>
<div class="chart-container">
<LayerCake
padding={{ top: 20, bottom: 80, left: 60, right:40 }}
x={xKey}
y={yKey}
yScale={scaleBand().paddingInner([0.15])}
xDomain={[0, null]}
data={data}
>
<Svg>
<AxisX gridlines={true} baseline={true} snapTicks={true} ticks="4" />
<AxisY gridlines={false} />
<Bar
/>
</Svg>
<Html
pointerEvents={false}
>
{#if hideTooltip !== true}
<Tooltip
{evt}
>
{#const tooltipData = {data}}
{#each Object.entries(tooltipData) as [key, value]}
{console.log('tooltipData',tooltipData)}
<div class="row">hi is this showing up?</div>
{/each}
</Tooltip>
{/if}
</Html>
</LayerCake>
</div>
<style>
.chart-container {
width: 600px;
height: 300px;
}
</style>
The other components are taken directly from the LayerCake framework.

I figured it out. Most important thing was to add a dispatch event in the Bar component, and create mouseover, movemove, focus events for the rects there.
Updated REPL: https://svelte.dev/repl/09725c92e4104d0cad53d0387a762269?version=3.55.1
App.svelte:
<script>
import { LayerCake, Svg, Html } from "layercake";
import Bar from "./Bar.svelte";
import AxisX from "./AxisX.svelte";
import AxisY from "./AxisY.svelte";
import Tooltip from "./Tooltip.html.svelte";
import { scaleBand } from "d3-scale";
let data = [
{
fruit: "Apple",
number: 364,
},
{
fruit: "Banana",
number: 263,
},
{
fruit: "Mango",
number: 872,
},
{
fruit: "Pear",
number: 156,
},
]
data.forEach((d) => {
d[xKey] = +d[xKey];
});
const xKey = "number";
const yKey = "fruit";
let evt;
let hideTooltip = false;
</script>
<div id="chart-container">
<LayerCake
padding={{ top: 20, bottom: 80, left: 60, right:40 }}
x={xKey}
y={yKey}
yScale={scaleBand().paddingInner([0.15])}
xDomain={[0, null]}
data={data}
>
<Svg>
<AxisX gridlines={true} baseline={true} snapTicks={true} ticks="4" />
<AxisY gridlines={false} />
<Bar
on:mousemove={event => evt = hideTooltip = event}
on:mouseout={() => (hideTooltip = true)}
/>
</Svg>
<Html
pointerEvents={false}
>
{#if hideTooltip !== true}
<Tooltip
{evt}
let:detail
>
{#const tooltipData = { ...detail.props }}
<div class="row">{tooltipData.fruit}: {tooltipData.number}</div>
</Tooltip>
{/if}
</Html>
</LayerCake>
</div>
<style>
#chart-container {
width: 600px;
height: 300px;
}
</style>
Bar.svelte:
<script>
import { getContext, createEventDispatcher } from "svelte";
const { data, xGet, yGet, xScale, yScale } = getContext('LayerCake');
export let fill = '#ef4136';
let hideTooltip = false;
const dispatch = createEventDispatcher();
function handleMousemove(feature) {
return function handleMousemoveFn(e) {
raise(this);
// When the element gets raised, it flashes 0,0 for a second so skip that
if (e.layerX !== 0 && e.layerY !== 0) {
dispatch("mousemove", { e });
}
};
}
</script>
<g
class="bar-group"
on:mouseout={(e) => dispatch("mouseout")}
on:blur={(e) => dispatch("mouseout")}
>
{#each $data as d, i}
<rect
class='group-rect'
data-id="{i}"
x="{$xScale.range()[0]}"
y="{$yGet(d)}"
height={$yScale.bandwidth()}
width="{$xGet(d)}"
{fill}
on:mouseover={(e) => dispatch("mousemove", { e, props: d })}
on:focus={(e) => dispatch("mousemove", { e, props: d })}
on:mousemove={(e) => handleMousemove(e, d)}
></rect>
{/each}
</g>
Tooltip.html.svelte, AxisY.svelte, and AxisX.svelte stay the same as they were (as they are in Layer Cake).

Related

Mediarecorder react records empty blob for iphone safari - React

Voice recording comes empty in safari iphone always, my code is as below for my common component, its working in safari on mac and chrome easily
import React, { useEffect, useRef, useState } from "react";
import { useRouter } from "next/router";
import PropTypes from "prop-types";
import { useReactMediaRecorder } from "react-media-recorder";
import WaveSurfer from "wavesurfer.js";
import MicrophonePlugin from "wavesurfer.js/dist/plugin/wavesurfer.microphone";
import Button from "#mui/material/Button";
import Grid from "#mui/material/Grid";
import Typography from "#mui/material/Typography";
import StyledButton from "../StyledButton";
import Fab from "#mui/material/Fab";
// Icons
import MicIcon from "#mui/icons-material/Mic";
import StopIcon from "#mui/icons-material/Stop";
import DeleteIcon from "#mui/icons-material/Delete";
import Waveform from "./Waveform";
import styled from "#emotion/styled";
const FabAudio = styled(Fab)`
background-color: #fd0d1b;
color: #fff;
&:hover {
background-color: #fd0d1b;
color: #fff;
}
`;
const Small = styled("div")`
font-size: 10px;
`;
function VoiceInput({
title,
required,
id,
totalQuestions,
primaryButtonTitle,
secondaryButtonTitle,
nextRoute,
handleEndSurvey,
handleResponse,
}) {
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const [blob, setBlob] = useState(null);
const router = useRouter();
const waveSurferRef = useRef();
const containerRef = useRef();
let timeout;
let context, processor;
const { status, startRecording, stopRecording, mediaBlobUrl, clearBlobUrl } =
useReactMediaRecorder({
audio: true,
onStop: (blobUrl, blob) => {
console.log("Testing blob" + blob);
setBlob(blob);
},
});
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
useEffect(() => {
if (status === "recording") {
if (isSafari) {
// Safari 11 or newer automatically suspends new AudioContext's that aren't
// created in response to a user-gesture, like a click or tap, so create one
// here (inc. the script processor)
let AudioContext = window.AudioContext || window.webkitAudioContext;
context = new AudioContext();
processor = context.createScriptProcessor(1024, 1, 1);
}
waveSurferRef.current = WaveSurfer.create({
container: containerRef.current,
responsive: true,
barWidth: 2,
height: 80,
barHeight: 3,
barMinHeight: 1,
barRadius: 3,
barWidth: 3,
barGap: 5,
cursorWidth: 0,
waveColor: "red",
plugins: [MicrophonePlugin.create()],
});
const microphone = waveSurferRef.current.microphone;
timeout = setTimeout(() => {
microphone.stop();
stopRecording();
waveSurferRef.current.destroy();
}, 60000);
microphone.start();
}
if (status === "stopped") {
const microphone = waveSurferRef.current.microphone;
microphone.stop();
waveSurferRef.current.destroy();
clearTimeout(timeout);
}
return () => {
if (status === "recording") {
const microphone = waveSurferRef.current.microphone;
microphone.stop();
waveSurferRef.current.destroy();
clearTimeout(timeout);
}
};
}, [status]);
const handleNext = async () => {
if (required && !mediaBlobUrl) {
setError(true);
setErrorMessage("Please record the message");
return;
}
if (["recording", "paused"].includes(status)) {
setError(true);
setErrorMessage("Please stop the recording");
return;
}
if (mediaBlobUrl) {
const uniqueId =
Date.now().toString(36) + Math.random().toString(36).substring(2);
const audiofile = new File([blob], `${uniqueId}.webm`, {
type: "audio/webm",
});
const isLastAnswer = +id === totalQuestions ? true : false;
const res = await handleResponse(audiofile, isLastAnswer);
if (isLastAnswer) {
res && handleEndSurvey();
} else {
res && router.push(nextRoute);
}
} else {
if (+id === totalQuestions) {
handleEndSurvey();
} else {
router.push(nextRoute);
}
}
};
const handlePrev = () => {
router.back();
};
const removeAudio = () => {
setError(false);
clearBlobUrl();
setBlob(null);
};
return (
<Grid container spacing={5} height="100%" alignItems="center">
{/* question section */}
<Grid item xs={12}>
<Typography variant="h2" fontWeight={550}>
{title}
</Typography>
</Grid>
{/*Input Section */}
<Grid item md={2} xs={0}></Grid>
<Grid item md={8} xs={12}>
{mediaBlobUrl && <Waveform audio={mediaBlobUrl} />}
{["recording", "paused"].includes(status) && (
<Grid container>
<Grid item xs={12} ref={containerRef}></Grid>
</Grid>
)}
{mediaBlobUrl && (
<div>
<FabAudio aria-label="add" sx={{ mt: 2 }} onClick={removeAudio}>
<DeleteIcon />
</FabAudio>
</div>
)}
{["idle", "stopped"].includes(status) && !mediaBlobUrl && (
<>
<FabAudio
color="secondary"
aria-label="add"
sx={{ mt: 2 }}
onClick={() => {
startRecording();
setError(false);
}}
>
<MicIcon />
</FabAudio>
<Typography mt={2}>Hit Record to Start</Typography>
<Small>Speak close to the microphone for better response.</Small>
</>
)}
{["recording", "paused"].includes(status) && (
<>
<FabAudio aria-label="add" onClick={stopRecording}>
<StopIcon />
</FabAudio>
</>
)}
{error && (
<Typography mt={2} color="red">
{errorMessage}
</Typography>
)}
</Grid>
<Grid item md={2} xs={0}></Grid>
{/* Button Section */}
<Grid item md={3} xs={0}></Grid>
{secondaryButtonTitle && (
<Grid item md={3} xs={12}>
<Button
sx={{ width: "160px" }}
variant="outlined"
onClick={handlePrev}
>
{secondaryButtonTitle}
</Button>
</Grid>
)}
<Grid item md={secondaryButtonTitle ? 3 : 6} xs={12}>
<StyledButton onClick={handleNext}>
{+id === totalQuestions ? "Submit" : primaryButtonTitle}
</StyledButton>
</Grid>
<Grid item md={3} xs={0}></Grid>
</Grid>
);
}
VoiceInput.propTypes = {
title: PropTypes.string,
required: PropTypes.bool,
id: PropTypes.string,
totalQuestions: PropTypes.number,
primaryButtonTitle: PropTypes.string,
secondaryButtonTitle: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
nextRoute: PropTypes.string,
};
export default VoiceInput;
The blob size shows up as 0, and shows type: audio/wav is that an issue?

Using useComboBox from DownShift with react-hook-form

I'm trying to use useComboBox from DownShift with react-hook-form and the value of the input is always undefined. I started with this: https://codesandbox.io/s/react-hook-form-controller-079xx?file=/src/DonwShift.js
And replaced the DownShift.js component with this: https://codesandbox.io/s/usecombobox-usage-1fs67?file=/src/index.js:168-438
Everything works except when I submit the value is undefined.What am I missing to set the value?
<form className="card" onSubmit={handleSubmit(handleShare)}>
<div className="body">
<Controller
as={Autocomplete}
control={control}
name="recipient"
items={userList}
/>
<button
className="secondaryActionBtn inputBtn"
type="submit"
enabled={String(formState.dirty)}
>
<FontAwesomeIcon icon={faPlus} />
</button>
{errors.lastname && 'Feed Name is required.'}
</div>
<footer></footer>
</form>
import React, { memo, useState } from 'react';
import PropTypes from 'prop-types';
import { useCombobox } from 'downshift';
const menuStyles = {
maxHeight: '180px',
overflowY: 'auto',
width: '135px',
margin: 0,
borderTop: 0,
background: 'white',
position: 'absolute',
zIndex: 1000,
listStyle: 'none',
padding: 0,
left: '135px'
};
const comboboxStyles = { display: 'inline-block', marginLeft: '5px' };
function Item({ isHighlighted, getItemProps, item, index }) {
return (
<li
style={isHighlighted ? { backgroundColor: '#bde4ff' } : {}}
key={`${item}${index}`}
{...getItemProps({ item, index })}
>
{item}
</li>
);
}
Item = memo(Item);
const Autocomplete = ({ items }) => {
const [inputItems, setInputItems] = useState(items);
const {
isOpen,
getToggleButtonProps,
getLabelProps,
getMenuProps,
getInputProps,
getComboboxProps,
highlightedIndex,
getItemProps
} = useCombobox({
items: inputItems,
onInputValueChange: ({ inputValue }) => {
setInputItems(
items.filter(item =>
item.toLowerCase().includes(inputValue.toLowerCase())
)
);
}
});
return (
<div>
<label htmlFor="recipient" {...getLabelProps()}>
Choose an element:
</label>
<div style={comboboxStyles} {...getComboboxProps()}>
<input name="recipient" {...getInputProps()} id="recipient" />
<button {...getToggleButtonProps()} aria-label="toggle menu">
↓
</button>
</div>
<ul {...getMenuProps()} style={menuStyles}>
{isOpen &&
inputItems.map((item, index) => (
<Item
key={item}
isHighlighted={highlightedIndex === index}
getItemProps={getItemProps}
item={item}
index={index}
/>
))}
</ul>
</div>
);
};
Autocomplete.propTypes = {
list: PropTypes.array
};
export default Autocomplete;
For others who get stuck on this here's how I solved it. The Controller in react-hook-form injects an onChange into the component as a prop. So i set the onSelectedItemChange prop in useCombobox hook to pass its value into onChange. Like this:
const {
isOpen,
getToggleButtonProps,
getLabelProps,
getMenuProps,
getInputProps,
getComboboxProps,
highlightedIndex,
getItemProps
} = useCombobox({
items: inputItems,
onSelectedItemChange: ({ inputValue }) => onChange(inputValue),
onInputValueChange: ({ inputValue }) => {
setInputItems(
items.filter(item =>
item.toLowerCase().includes(inputValue.toLowerCase())
)
);
}
});

How to use Radio groups inside Antd table?

I want to do this: each row is a Radio group, each cell is a Radio button, like the picture:
An example of Radio group is like:
<Radio.Group onChange={this.onChange} value={this.state.value}>
<Radio value={1}>A</Radio>
<Radio value={2}>B</Radio>
<Radio value={3}>C</Radio>
<Radio value={4}>D</Radio>
</Radio.Group>
But I don't know how to add a Radio group to wrap each Antd table row?
My current code is:
renderTable() {
let columns = [];
columns.push(
{
title: '',
dataIndex: 'name',
key: 'name',
width: '45vw',
},
);
this.props.task.options.forEach((option, i) => {
columns.push(
{
title: option,
dataIndex: option,
key: option,
className: 'choice-table-column',
render: x => {
return <Radio value={0} />
},
},
);
});
let rowHeaders = [];
this.props.task.extras.forEach((extra, i) => {
rowHeaders.push(
{"name": `${i + 1}. ${extra}`},
);
});
// How can I pass a className to the Header of a Table in antd / Ant Design?
// https://stackoverflow.com/questions/51794977/how-can-i-pass-a-classname-to-the-header-of-a-table-in-antd-ant-design
const tableStyle = css({
'& thead > tr > th': {
textAlign: 'center',
},
'& tbody > tr > td': {
textAlign: 'center',
},
'& tbody > tr > td:first-child': {
textAlign: 'left',
},
});
return (
<div>
<Table className={tableStyle} columns={columns} dataSource={rowHeaders} size="middle" bordered pagination={false} />
</div>
);
}
I don't think it is possible to use radio group for each row, however you can achieve it in a traditional way.
Here is code sample
https://codesandbox.io/s/goofy-benz-12kv5
class App extends React.Component {
state = {
task: { options: [1, 2, 3, 4, 5], extras: [6, 7, 8, 9, 10] },
selected: {}
};
onRadioChange = e => {
let name = e.currentTarget.name;
let value = e.currentTarget.value;
this.setState({
...this.state,
selected: { ...this.state.selected, [name]: value }
});
};
onSubmit = () => {
console.log(this.state.selected);
this.setState({
...this.state,
selected: {}
});
};
render() {
let columns = [];
columns.push({
title: "",
dataIndex: "name",
key: "name",
width: "45vw"
});
this.state.task.options.forEach((option, i) => {
columns.push({
title: option,
key: option,
render: row => {
return (
<input
type="radio"
checked={this.state.selected[row.name] == option}
onChange={this.onRadioChange}
name={row.name}
value={option}
/>
);
}
});
});
let rowHeaders = [];
this.state.task.extras.forEach((extra, i) => {
rowHeaders.push({ name: `${i + 1}.${extra}` });
});
return (
<div>
<Button onClick={this.onSubmit} type="primary">
{" "}
Submit
</Button>
<Table
columns={columns}
dataSource={rowHeaders}
size="middle"
bordered
pagination={false}
/>
<Tag color="red">Selected options</Tag>
<br />
{JSON.stringify(this.state.selected)}
</div>
);
}
}
hi there i had the same problem and base on new updates on antd this way of using is easier
<Table
rowSelection={{
type: "radio",
getCheckboxProps: (record) => {
console.log("record", record);
},
}}
pagination={{ hideOnSinglePage: true }}
columns={columns}
dataSource={data}
/>
example : https://ant.design/components/table/#components-table-demo-row-selection
for hiding table header : https://newbedev.com/javascript-antd-table-hide-table-header-code-example
hope its usefull

How to prevent the creation of a port from spreading to all objects, in GoJS?

When user interaction creates a port in an object (right click on the object then "add left port"), all objects get the same port added, the same for the object in the palette.
How can I prevent the creation of a port from spreading to all objects?
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LLDynp</title>
<meta name="description" content="Nodes with varying lists of ports on each of four sides." />
<!-- Copyright 1998-2017 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="../release/go-debug.js"></script>
<span id="diagramEventsMsg" style="color: red"></span>
<span id="changeMethodsMsg" style="color: red"></span>
<span id="BackgroundDoubleClicked" style="color: red"></span>
<p>
<script id="code">
function init() {
var $ = go.GraphObject.make; //for conciseness in defining node templates
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
allowDrop: true, // from Palette
mouseDrop: function(e) { finishDrop(e, null); },
"commandHandler.archetypeGroupData": { isGroup: true, category: "OfGroups" },
"undoManager.isEnabled": true
});
// this function is used to highlight a Group that the selection may be dropped into
function highlightGroup(e, grp, show) {
if (!grp) return;
e.handled = true;
if (show) {
// cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
// instead depend on the DraggingTool.draggedParts or .copiedParts
var tool = grp.diagram.toolManager.draggingTool;
var map = tool.draggedParts || tool.copiedParts; // this is a Map
// now we can check to see if the Group will accept membership of the dragged Parts
if (grp.canAddMembers(map.toKeySet())) {
grp.isHighlighted = true;
return;
}
}
grp.isHighlighted = false;
}
function finishDrop(e, grp) {
var ok = (grp !== null
? grp.addMembers(grp.diagram.selection, true)
: e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
if (!ok) e.diagram.currentTool.doCancel();
}
// LL
function showMessage(s) {
document.getElementById("diagramEventsMsg").textContent = s;
}
// To simplify this code we define a function for creating a context menu button:
function makeButton(text, action, visiblePredicate) {
return $("ContextMenuButton",
$(go.TextBlock, text),
{ click: action },
//{showMessage( "button: " + myDiagram.lastInput.button);
// don't bother with binding GraphObject.visible if there's no predicate
visiblePredicate ? new go.Binding("visible", "", function(o, e) { return o.myDiagram ? visiblePredicate(o, e) : false; }).ofObject() : {});
//}
}
var nodeMenu = // context menu for each Node
$(go.Adornment, "Horizontal",
makeButton("Add left port",
function (e, obj) { addPort("left"); }),
makeButton("Add right port",
function (e, obj) { addPort("right"); }),
); //Leon end nodeMenu
// Add a port to the specified side of the selected nodes.
function addPort(side) {
myDiagram.startTransaction("addPort");
myDiagram.selection.each(function(node) {
// skip any selected Links
if (!(node instanceof go.Node)) return;
// compute the next available index number for the side
var i = 0;
while (node.findPort(side + i.toString()) !== node) i++;
// now this new port name is unique within the whole Node because of the side prefix
var name = side + i.toString();
// get the Array of port data to be modified
var arr = node.data[side + "Array"];
showMessage ("node: " + node + ";name: " + name + ";arr: " + arr + ";node.data: " + node.data + "; node.data[side=" + node.data[side + "Array"]);
if (arr) { console.log("arr is true")
// create a new port data object
var newportdata = {
portId: name,
portColor: "rgb(180, 0, 0)" //go.Brush.randomColor()
// if you add port data properties here, you should copy them in copyPortData above
};
// and add it to the Array of port data
myDiagram.model.insertArrayItem(arr, -1, newportdata);
}
});
myDiagram.commitTransaction("addPort");
}
var portSize = new go.Size(10, 10);
myDiagram.linkTemplate =
$(go.Link,
{
routing: go.Link.Orthogonal, corner: 5,
relinkableFrom: true, relinkableTo: true
},
$(go.Shape, { stroke: "gray", strokeWidth: 2 }),
$(go.Shape, { stroke: "gray", fill: "gray", toArrow: "Standard" })
);
////////////// groupTemplateMap ///////////////////////////////////
myDiagram.groupTemplateMap.add("OfGroups",
$(go.Group, "Table",// left ports+ placeHolder+right ports
{ locationObjectName: "BODY",
selectionObjectName: "PH",
resizable: true,
resizeObjectName: "PH",
contextMenu: nodeMenu,
background: "transparent",
mouseDragEnter: function(e, grp, prev) { highlightGroup(e, grp, true); },
mouseDragLeave: function(e, grp, next) { highlightGroup(e, grp, false); },
computesBoundsAfterDrag: false,
mouseDrop: finishDrop,
handlesDragDropForMembers: true // don't need to define handlers on member Nodes and Links
},
new go.Binding("background", "isHighlighted", function(h) { return h ? "rgba(255,0,0,0.2)" : "transparent"; }).ofObject(),
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// the body
$(go.Panel, "Auto",
{ row: 1, column: 1, name: "BODY"
//stretch: go.GraphObject.Fill
},
$(go.Shape, "Rectangle",
{ fill: null, stroke: "rgb(0, 0, 200)", strokeWidth: 3,name: "PH",
minSize: new go.Size(56, 56) }),
$(go.Panel, "Vertical", // title above Placeholder
{alignment: go.Spot.Top},
$(go.Panel, "Horizontal", // button next to TextBlock
{ stretch: go.GraphObject.Horizontal, background: "#FFDD33" },
$("SubGraphExpanderButton",
{ alignment: go.Spot.Right, margin: 5 }),
$(go.TextBlock,
{
alignment: go.Spot.Left,
editable: true,
margin: 5,
font: "bold 18px sans-serif",
opacity: 0.75,
stroke: "#404040"
},
new go.Binding("text", "text").makeTwoWay())
), // end Horizontal Panel
$(go.Placeholder,
{ padding: 5
, alignment: go.Spot.TopLeft // no change
},
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify))
) // end Vertical Panel
), // end Auto Panel body
// the Panel holding the left port elements, which are themselves Panels,
// created for each item in the itemArray, bound to data.leftArray
$(go.Panel, "Vertical",
new go.Binding("itemArray", "leftArray"),
{ row: 1, column: 0,
itemTemplate:
$(go.Panel,
{ _side: "left", // internal property to make it easier to tell which side it's on
fromSpot: go.Spot.Left, toSpot: go.Spot.Left,
fromLinkable: true, toLinkable: true, cursor: "pointer"},
new go.Binding("portId", "portId"),
$(go.Shape, "Rectangle",
{ stroke: null, strokeWidth: 0,
desiredSize: portSize,
margin: new go.Margin(1,0) },
new go.Binding("fill", "portColor"))
) // end itemTemplate
}
), // end Vertical Panel
// the Panel holding the right port elements, which are themselves Panels,
// created for each item in the itemArray, bound to data.rightArray
$(go.Panel, "Vertical",
new go.Binding("itemArray", "rightArray"),
{ row: 1, column: 2,
itemTemplate:
$(go.Panel,
{ _side: "right",
fromSpot: go.Spot.Right, toSpot: go.Spot.Right,
fromLinkable: true, toLinkable: true, cursor: "pointer"},
new go.Binding("portId", "portId"),
$(go.Shape, "Rectangle",
{ stroke: null, strokeWidth: 0,
desiredSize: portSize,
margin: new go.Margin(1, 0) },
new go.Binding("fill", "portColor"))
) // end itemTemplate
}
), // end Vertical Panel
)
); // end groupTemplateMap.add("OfGroups"
var nodeDataArray = [];
var linkDataArray = [];
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
// initialize the Palette and its contents
myPalette =
$(go.Palette, "myPaletteDiv",
{
groupTemplateMap: myDiagram.groupTemplateMap
// ,layout: $(go.GridLayout, { wrappingColumn: 1, alignment: go.GridLayout.Position })
});
myPalette.model = new go.GraphLinksModel([
{ //key: 101,
text: 'UNIT',
leftArray: [], rightArray: [],
isGroup: true, isSubProcess: true,
category: "OfGroups", isAdHoc: true,
loc: '0 0' },
]);
}
</script>
</head>
<body onload="init()">
<!-- LL -->
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<div id="sample">
<div style="width: 100%; display: flex; justify-content: space-between">
<div id="myPaletteDiv" style="width: 100px; margin-right: 2px; background-color: whitesmoke; border: solid 1px black"></div>
<div id="myDiagramDiv" style="flex-grow: 1; height: 480px; border: solid 1px black"></div>
</div>
</div>
</div>
</body>
</html>
Please read the code for the Dynamic Ports sample at https://gojs.net/latest/samples/dynamicPorts.html. Note the comments in the load function.
function load() {
myDiagram.model = go.Model.fromJson(. . .);
// When copying a node, we need to copy the data that the node is bound to.
// This JavaScript object includes properties for the node as a whole, and
// four properties that are Arrays holding data for each port.
// Those arrays and port data objects need to be copied too.
// Thus Model.copiesArrays and Model.copiesArrayObjects both need to be true.
// Link data includes the names of the to- and from- ports;
// so the GraphLinksModel needs to set these property names:
// linkFromPortIdProperty and linkToPortIdProperty.
}
And notice in the JSON representation of the model how it sets those properties:
{ "class": "go.GraphLinksModel",
"copiesArrays": true,
"copiesArrayObjects": true,
"linkFromPortIdProperty": "fromPort",
"linkToPortIdProperty": "toPort",
"nodeDataArray": [
{"key":1, "name":"Unit One", "loc":"101 204",
. . .
So when you create a GraphLinksModel programmatically, be sure to set those same properties to the values that are appropriate for your data.

How can I update chart with state change

When I am listing the new data it updates but then the chart only updates the first time. I don't have access inside the Line component so I can't do componentWillReceiveProps(nextProps) for that one. How can I update the chart with each new state change?
import React, { Component } from 'react';
import {Line} from 'react-chartjs-2';
class Chart extends Component {
componentWillReceiveProps(nextProps) {
debugger
this.setState({ data: nextProps.info });
}
constructor(props) {
super(props);
this.state = {
data: []
}
}
render() {
let chart = {
chartData:{
labels: this.state.data,
datasets:[
{
label: 'Weight',
data: this.state.data,
backgroundColor:[
'rgba(54, 162, 235, 0.6)',
'rgba(255, 206, 86, 0.6)',
]
}
]
}
}
return (
<div className="chart" style={{height: 200 + "px", width: 100 + "%"}}>
<Line
data={chart.chartData}
width={100}
height={50}
options={{
maintainAspectRatio: false
}}
/>
<ul>
{this.state.data.map(val=> <li>{val}</li>)}
</ul>
</div>
);
}
}

Resources