Skip to content

Commit

Permalink
Implement the network graph
Browse files Browse the repository at this point in the history
  • Loading branch information
ysbrandB committed Jun 12, 2024
1 parent 0bad1ae commit d32e108
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 70 deletions.
3 changes: 3 additions & 0 deletions .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,7 @@ AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

#the id of the Item that is python. This is used to link everything together
PYTHON_ID=1

VITE_APP_NAME="${APP_NAME}"
2 changes: 2 additions & 0 deletions .env.prod
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
PHP_VERSION=8.3
NODE_VERSION=20
#the id of the Item that is python. This is used to link everything together
PYTHON_ID=1
18 changes: 7 additions & 11 deletions app/Http/Controllers/ItemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,12 @@ public function store(Request $request)
$wiringPhoto?->storeAs('photos/' . $photo->hashName(), ['disk' => 'public']);
$item = new Item();
$item->fill($request->except('attributes', 'photo', 'wiring_photo', 'json_items'));
$item->json_items = array_map('intval', explode(',', $request->input('edges')));
$item->json_items = array_map('intval', explode(',', $request->input('edges')));
$item->photo = $photo?->hashName();
$item->wiring_photo = $wiringPhoto?->hashName();
$item->save();
$string = explode(',', $request->input('attributes'));
if($string && $string[0]!==''){
$item->attributes()->sync($string);
}
$item->attributes()->sync($string[0] === "" ? [] : $string);
return to_route('items.show', ['public_id' => $item->public_id]);
}

Expand All @@ -74,7 +72,7 @@ public function store(Request $request)
public function show(string $publicId)
{
$id = Hashids::decode($publicId);
if(!$id || !isset($id[0])) {
if (!$id || !isset($id[0])) {
abort(404);
}
$item = Item::with('attributes', 'attributes.attributeType')->findOrFail($id[0]);
Expand Down Expand Up @@ -111,7 +109,7 @@ public function update(Request $request, int $id)
{
$item = Item::findOrFail($id);
$photo = $request->file('photo');
if($photo){
if ($photo) {
$photo->storeAs('photos/' . $photo->hashName(), ['disk' => 'public']);
if ($item->photo) {
//delete the old photo
Expand All @@ -121,7 +119,7 @@ public function update(Request $request, int $id)
}

$wiring_photo = $request->file('wiring_photo');
if($wiring_photo){
if ($wiring_photo) {
$wiring_photo->storeAs('photos/' . $wiring_photo->hashName(), ['disk' => 'public']);

if ($item->wiring_photo) {
Expand All @@ -132,12 +130,10 @@ public function update(Request $request, int $id)
}

$item->fill($request->except('attributes', 'photo', 'wiring_photo', 'json_items'));
$item->json_items = array_map('intval', explode(',', $request->input('edges')));
$item->json_items = array_map('intval', explode(',', $request->input('edges')));
$item->save();
$string = explode(',', $request->input('attributes'));
if($string && $string[0]!==''){
$item->attributes()->sync($string);
}
$item->attributes()->sync($string[0] === "" ? [] : $string);
return to_route('items.show', ['public_id' => $item->public_id]);
}

Expand Down
6 changes: 3 additions & 3 deletions database/factories/ItemFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ public function definition(): array
'title' => $this->faker->name,
'description' => $this->faker->paragraph,
'card_description' => $this->faker->text,
'photo' => 'Af2XzLDwCqixQw8LcnJm5mATRiBHkXIJCVVdnGYf.jpg',
'photo' => 'wQb7eXES6yeZYqAg5bVuibbWMYLB3UyqWz2OvJy1.jpg',
'is_actuator' => $this->faker->boolean,
'pros' => $this->faker->paragraph,
'cons' => $this->faker->paragraph,
'hardware_considerations' => $this->faker->paragraph,
'software_considerations' => $this->faker->paragraph,
'example_code' => $this->faker->paragraph,
'wiring_photo' => 'Af2XzLDwCqixQw8LcnJm5mATRiBHkXIJCVVdnGYf.jpg',
'wiring_photo' => 'wQb7eXES6yeZYqAg5bVuibbWMYLB3UyqWz2OvJy1.jpg',
'wiring_instructions' => $this->faker->paragraph,
'json_items' => [1, 2, 3, 4, 5],
'json_items' => [],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('items', function (Blueprint $table) {
$table->text('wiring_instructions')->change();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('items', function (Blueprint $table) {
$table->string('wiring_instructions')->change();
});
}
};
19 changes: 11 additions & 8 deletions database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,23 @@ public function run(): void
Attribute::factory()->count(5)
)->create();


Item::factory()->create([
'title'=>'Python',
]);

$items = Item::factory(10)->create();
for($i = 2; $i < 11-3; $i+=3) {
$item = $items[$i];
$item->json_items = [$i, $i + 1, $i + 2];
$item->save();
}

$attributes = Attribute::all();
foreach ($items as $item) {
$item->attributes()->attach(
$attributes->random(3)
);
}

for($i = 0; $i < sizeof($items) - 1; $i++) {
Edge::create([
'from_item_id' => $items->get($i)->id,
'to_item_id' => $items->get($i+1)->id,
'belongsto_item_id' => $items->first()->id,
]);
}
}
}
3 changes: 2 additions & 1 deletion resources/js/Pages/Items/Edit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const form = useForm({
software_considerations: props.item?.software_considerations ?? '',
example_code: props.item?.example_code ?? '',
edges: props.item?.json_items ?? [],
attributes: flattenedSelectedAttributes.value,
photo: '',
wiring_photo: '',
});
Expand Down Expand Up @@ -89,7 +90,7 @@ const submit = () => {
return router.get(route('items.show', props.item.public_id));
return router.get(route('items.index'));
}
console.error(data?.data?.response.data);
console.log(data);
}
).catch((error) => {
console.log(error);
Expand Down
165 changes: 119 additions & 46 deletions resources/js/Pages/Test.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,60 @@
<script setup lang="ts">
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import {Head} from '@inertiajs/vue3';
import {Head, router} from '@inertiajs/vue3';
const props = defineProps<{
json_items: any;
items: Item[],
python: Item
}>();
console.log(props.json_items);
import { VNetworkGraph } from "v-network-graph"
import {VNetworkGraph} from "v-network-graph"
import "v-network-graph/lib/style.css"
import 'd3-force';
import { reactive, ref, watch } from "vue"
import {reactive} from "vue"
import * as vNG from "v-network-graph"
import {
ForceLayout,
ForceNodeDatum,
ForceEdgeDatum,
} from "v-network-graph/lib/force-layout"
import Card from "@/Components/Card.vue";
import {Item} from "@/types";
interface Node extends vNG.Node {
size?: number
color?: string
label?: boolean
}
interface Edge extends vNG.Edge {
width?: number
color?: string
dashed?: boolean
}
const nodeCount = ref(20)
const myEdges: Set<Edge> = new Set<Edge>();
const nodes = reactive({})
const edges = reactive({})
// initialize network
buildNetwork(nodeCount.value, nodes, edges)
if(props.python) {
//@ts-ignore
nodes[`node${props.python.id}`] = {name: props.python.title, photo_url: props.python.photo_url, public_id: props.python.public_id}
}
props.items.forEach((item: Item) => {
console.log(item.photo_url)
//@ts-ignore
nodes[`node${item.id}`] = {name: item.title, photo_url: item.photo_url, public_id: props.python.public_id}
//@ts-ignore
const list = [item.id, ...item.json_items, props.python.id]
for(let i=0; i<list.length - 1; i++){
myEdges.add({source: `node${list[i]}`, target: `node${list[i+1]}`})
}
})
//filter out the myEdges where source is target and target is source from another item
Array.from(myEdges).forEach((edge: Edge, index: number) => {
if(!myEdges.has({source: edge.target, target: edge.source})){
//@ts-ignore
edges[`edge${index}`] = edge
}
})
const configs = reactive(
vNG.defineConfigs({
Expand All @@ -44,55 +76,96 @@ const configs = reactive(
}),
},
node: {
selectable: false,
normal: {
radius: 20,
},
label: {
visible: false,
visible: true,
fontFamily: undefined,
fontSize: 11,
lineHeight: 1.1,
color: "#000000",
margin: 4,
direction: "south",
text: "name",
},
},
})
)
function buildNetwork(count: number, nodes: vNG.Nodes, edges: vNG.Edges) {
const idNums = [...Array(count)].map((_, i) => i)
// nodes
const newNodes = Object.fromEntries(idNums.map(id => [`node${id}`, {}]))
Object.keys(nodes).forEach(id => delete nodes[id])
Object.assign(nodes, newNodes)
// edges
const makeEdgeEntry = (id1: number, id2: number) => {
return [`edge${id1}-${id2}`, { source: `node${id1}`, target: `node${id2}` }]
}
const newEdges = Object.fromEntries([
...idNums
.map(n => [n, (Math.floor(n / 4) * 4) % count])
.map(([n, m]) => (n === m ? [n, (n + 4) % count] : [n, m]))
.map(([n, m]) => makeEdgeEntry(n, m)),
])
Object.keys(edges).forEach(id => delete edges[id])
Object.assign(edges, newEdges)
}
</script>

<template>
<Head title="Dashboard"/>

<AuthenticatedLayout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">Dashboard</h2>
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">NodeOverview</h2>
</template>
<div class="demo-control-panel">
<label>Node count:</label>
<el-input-number v-model="nodeCount" :min="3" :max="200" />
<label>(&lt;= 200)</label>
</div>
<card>
<v-network-graph
class="w-full h-screen"
:nodes="nodes"
:edges="edges"
:configs="configs"
>
<defs>
<!--
Define the path for clipping the face image.
To change the size of the applied node as it changes,
add the `clipPathUnits="objectBoundingBox"` attribute
and specify the relative size (0.0~1.0).
-->
<clipPath id="faceCircle" clipPathUnits="objectBoundingBox">
<circle cx="0.5" cy="0.5" r="0.5" />
</clipPath>
</defs>

<v-network-graph
class="h-screen w-full"
:zoom-level="0.5"
:nodes="nodes"
:edges="edges"
:configs="configs"
/>
<!-- Replace the node component -->
<template #override-node="{ nodeId, scale, config, ...slotProps }">
<!-- circle for filling background -->
<circle
class="face-circle"
:r="config.radius * scale"
fill="#ffffff"
v-bind="slotProps"
/>
<image
class="face-picture"
:x="-config.radius * scale"
:y="-config.radius * scale"
:width="config.radius * scale * 2"
:height="config.radius * scale * 2"
:xlink:href="
//@ts-ignore
`${nodes[nodeId].photo_url}`"
clip-path="url(#faceCircle)"
/>
<!-- circle for drawing stroke -->
<circle
@click="router.get(route('items.show',
// @ts-ignore
{public_id: nodes[nodeId].public_id}))"
class="face-circle"
:r="config.radius * scale"
fill="none"
stroke="#808080"
:stroke-width="1 * scale"
v-bind="slotProps"
/>
</template>
</v-network-graph>
</card>
</AuthenticatedLayout>
</template>

<style lang="css" scoped>
.face-circle,
.face-picture {
transition: all 0.1s linear;
}
.face-picture {
object-fit: cover;
pointer-events: none;
}
</style>
4 changes: 3 additions & 1 deletion routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
Route::resource('attributes', AttributeController::class);
Route::get('/test', function () {
return Inertia::render('Test', [
'json_items'=>Item::query()->select('json_items', 'id')->without('attributes')->get()
'items'=>Item::query()->whereNot('id', env('PYTHON_ID'))->select('json_items', 'id', 'title', 'photo')->without('attributes')->get(),
'python'=>Item::query()->where('id', env('PYTHON_ID'))->select('json_items', 'id', 'title', 'photo')->without('attributes')->firstOrFail()
]);
})->name('test');

Route::get('/choice-helper', function () {
return Inertia::render('Dashboard');
})->name('dashboard');
Expand Down

0 comments on commit d32e108

Please sign in to comment.